day04 spring 声明式事务
创始人
2024-02-03 23:54:15

day04 spring 声明式事务

1.JDBCTemplate

1.1 简介

为了在特定领域帮助我们简化代码,Spring 封装了很多 『Template』形式的模板类。例如:RedisTemplate、RestTemplate 等等,包括我们今天要学习的 JDBCTemplate。

1.2 准备工作

1.2.1 加入依赖


4.0.0com.atguiguday50spring-JDBCTemplate1.0-SNAPSHOTorg.springframeworkspring-context5.3.1org.springframeworkspring-orm5.3.1org.springframeworkspring-test5.3.1junitjunit4.12testmysqlmysql-connector-java8.0.19com.alibabadruid1.0.31org.projectlomboklombok1.18.8provided

1.2.2 数据源的属性文件jdbc.properties

datasource.url=jdbc:mysql://localhost:3306/mybatis2?characterEncoding=utf8&serverTimezone=UTC
datasource.driver=com.mysql.cj.jdbc.Driver
datasource.username=root
datasource.password=123456

1.2.3 spring配置文件


1.2.4 创建Soldier实体类

package com.atguigu.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
// 全参构造器
@AllArgsConstructor
// 无参构造器
@NoArgsConstructor
public class Soldier {private Integer soldierId;private String soldierName;private String soldierWeapon;
}

1.2.5 创建SoldierDao接口

package com.atguigu.dao;import com.atguigu.pojo.Soldier;import java.sql.SQLException;
import java.util.List;public interface SoldierDao {/*** 根据id删除* @param soldierId*/void deleteById(Integer soldierId) throws SQLException;/*** 更新士兵对象* @param soldier*/void update(Soldier soldier) throws SQLException;/*** 添加士兵对象* @param soldier*/void add(Soldier soldier) throws SQLException;/*** 根据id查询士兵* @param soldierId* @return*/Soldier getById(Integer soldierId) throws SQLException;/*** 获取所有士兵* @return*/List findAll() throws SQLException;
}

1.2.6 创建SoldierDaoImpl实现类 JDBCTemplate的基本用法

package com.atguigu.dao.impl;import com.atguigu.dao.SoldierDao;
import com.atguigu.pojo.Soldier;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;import java.sql.SQLException;
import java.util.List;// 持久层注解
@Repository
public class SoldierDaoImpl implements SoldierDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic void deleteById(Integer soldierId) throws SQLException {String sql = "delete from t_soldier where soldier_id=?";jdbcTemplate.update(sql, soldierId);}@Overridepublic void update(Soldier soldier) throws SQLException {String sql = "update t_soldier set soldier_name=?,t_soldier_weapon=? where soldier_id=?";jdbcTemplate.update(sql, soldier.getSoldierName(),soldier.getSoldierWeapon(),soldier.getSoldierId());}@Overridepublic void add(Soldier soldier) throws SQLException {String sql = "insert into t_soldier(soldier_name,soldier_weapon) values(?,?)";jdbcTemplate.update(sql, soldier.getSoldierName(), soldier.getSoldierWeapon());}@Overridepublic Soldier getById(Integer soldierId) throws SQLException {String sql = "select soldier_id soldierId,soldier_name soldierName,soldier_weapon soldierWeapon from t_soldier where soldier_id=?";return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Soldier.class), soldierId);}@Overridepublic List findAll() throws SQLException {String sql = "select soldier_id soldierId,soldier_name soldierName,soldier_weapon soldierWeapon from t_soldier";return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Soldier.class));}
}

1.2.7 spring配置文件中添加包扫描注解

1.2.8 测试代码

package com.atguigu;import com.atguigu.dao.SoldierDao;
import com.atguigu.dao.impl.SoldierDaoImpl;
import com.atguigu.pojo.Soldier;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.sql.SQLException;
import java.util.List;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class JDBCTest {@Autowiredprivate SoldierDao soldierDao;@Testpublic void testFindAll() throws SQLException {List all = soldierDao.findAll();for (Soldier soldier : all) {System.out.println(soldier);}}@Testpublic void testFindOne() throws SQLException {System.out.println(soldierDao.getById(1));}
}

在这里插入图片描述

2.声明式事务的概述

2.1 编程式事务

事务功能的相关操作全部通过自己编写代码来实现:

Connection conn = ...;try {// 开启事务:关闭事务的自动提交conn.setAutoCommit(false);// 核心操作// 提交事务conn.commit();}catch(Exception e){// 回滚事务conn.rollBack();}finally{//将连接的autoCommit还原成trueconn.setAutoCommit(true);// 释放数据库连接conn.close();
}

编程式的实现方式存在缺陷:

  • 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
  • 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。

2.2 声明式事务

既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。

封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。

  • 好处1:提高开发效率
  • 好处2:消除了冗余的代码
  • 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化

所以,我们可以总结下面两个概念:

  • 编程式自己写代码实现事务控制
  • 声明式:通过配置spring框架实现事务控制

2.3 事务管理器

Spring中的声明式事务是通过事务管理器来进行事务管理的,所以在Spring中定义了事务管理器的顶级接口,针对各种不同的持久层框架,又定义了不同的事务管理器类来进行事务管理

2.3.1 顶级接口

2.3.1.1 spring5.2以前
public interface PlatformTransactionManager {TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;void commit(TransactionStatus status) throws TransactionException;void rollback(TransactionStatus status) throws TransactionException;
}
2.3.1.2 spring5.2以后

PlatformTransactionManager 接口本身没有变化,它继承了 TransactionManager

public interface TransactionManager {}

TransactionManager接口中什么都没有,但是它还是有存在的意义——定义一个技术体系。

2.3.2 事务管理器的体系结构

在这里插入图片描述

我们现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 Mybatis 用的也是这个类。

DataSourceTransactionManager类中的主要方法:

  • doBegin():开启事务
  • doSuspend():挂起事务(暂停事务)
  • doResume():恢复挂起的事务
  • doCommit():提交事务
  • doRollback():回滚事务

如果持久层使用Hibernate框架的话,则需要使用HibernateTransactionManager

3.基于注解的声明式事务

3.1 准备工作

3.1.1 引入依赖


4.0.0com.atguiguday50spring-transcation1.0-SNAPSHOTorg.springframeworkspring-context5.3.1org.springframeworkspring-orm5.3.1org.springframeworkspring-test5.3.1junitjunit4.13testmysqlmysql-connector-java8.0.27com.alibabadruid1.0.31org.projectlomboklombok1.18.8providedch.qos.logbacklogback-classic1.2.3

3.1.2 数据源的属性文件 jdbc.properties

datasource.url=jdbc:mysql://localhost:3306/mybatis2?characterEncoding=utf8&serverTimezone=UTC
datasource.driver=com.mysql.cj.jdbc.Driver
datasource.username=root
datasource.password=123456

3.1.3 spring配置文件




3.1.4 数据建模

3.1.4.1 物理建模
CREATE TABLE t_account(account_id INT PRIMARY KEY AUTO_INCREMENT,account_name VARCHAR(20),money DOUBLE
);INSERT INTO t_account VALUES (NULL,'zs',1000);INSERT INTO t_account VALUES (NULL,'ls',1000);
3.1.4.2 逻辑建模
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {private Integer accountId;private String accountName;private Double money;
}

3.1.5 创建持久层组件

3.1.5.1 AccountDao接口
package com.atguigu.dao;public interface AccountDao {/*** 修改用户的金额* @param id* @param money*/void updateAccountMoney(Integer id, Integer money);
}
3.1.5.2 AccountDaoImpl实现类
package com.atguigu.dao.impl;import com.atguigu.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;@Repository
public class AccountDaoImpl implements AccountDao {@Autowiredprivate JdbcTemplate jdbcTemplatel;@Overridepublic void updateAccountMoney(Integer id, Integer money) {String sql = "update t_account set money=money+? where account_id=?";jdbcTemplatel.update(sql, money, id);}
}

3.1.6 创建业务层组件

3.1.6.1 AccountService接口
package com.atguigu.serivce;public interface AccountService {/*** 转账方法* @param fromId 转出账户的id* @param toId 转入账户的id* @param money 转账金额*/void transfer(Integer fromId,Integer toId,Integer money);
}
3.1.6.2 AccountServiceImpl实现类
package com.atguigu.serivce.impl;import com.atguigu.dao.AccountDao;
import com.atguigu.serivce.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;@Overridepublic void transfer(Integer fromId, Integer toId, Integer money) {// 1.转出账户扣款accountDao.updateAccountMoney(fromId, -money);// 测试报错是否会回滚int num = 10 / 0;// 2.转入账户收款accountDao.updateAccountMoney(toId, money);}
}

3.1.7 创建AccounrController类

package com.atguigu.controller;import com.atguigu.serivce.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class AccountController {@Autowiredprivate AccountService accountService;public void transfer(Integer fromId,Integer toId,Integer money){accountService.transfer(fromId, toId, money);// 转账成功System.out.println("转账成功!!!");}
}

3.1.8 创建测试类进行测试

package com.atguigu;import com.atguigu.controller.AccountController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class TestTransaction {@Autowiredprivate AccountController accountController;@Testpublic void testTransfer(){accountController.transfer(1, 2, 500);}
}

因为没有添加事务,所以报错后并没有进行回滚

在这里插入图片描述

在这里插入图片描述

3.2 进行基于注解的声明式事务的配置

3.2.1 配置事务管理器

在spring的配置文件中配置事务管理器对象

3.2.2 开启基于注解的声明式事务功能

在spring的配置文件中开启基于注解的声明式事务功能



注意:导入名称空间时有好几个重复的,我们需要的是 tx 结尾的那个。

在这里插入图片描述

3.2.3 在需要事务的方法上使用注解

package com.atguigu.serivce.impl;import com.atguigu.dao.AccountDao;
import com.atguigu.serivce.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;@Transactional@Overridepublic void transfer(Integer fromId, Integer toId, Integer money) {// 1.转出账户扣款accountDao.updateAccountMoney(fromId, -money);// 测试报错是否会回滚int num = 10 / 0;// 2.转入账户收款accountDao.updateAccountMoney(toId, money);}
}

3.2.4 执行测试

package com.atguigu;import com.atguigu.controller.AccountController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class TestTransaction {@Autowiredprivate AccountController accountController;@Testpublic void testTransfer(){accountController.transfer(1, 2, 500);}
}

在这里插入图片描述

3.3 从日志内容角度查看事务效果

3.3.1 加入依赖

        ch.qos.logbacklogback-classic1.2.3

3.3.2 加入logback的配置文件

文件名:logbcak.xml


[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n

3.3.3 观察日志打印

在这里插入图片描述

3.4 debug查看事务管理器中的关键方法

需要查看的类:org.springframework.jdbc.datasource.DataSourceTransactionManager

3.4.1 开启事务的方法

在这里插入图片描述

3.4.2 提交事务的方法

在这里插入图片描述

3.4.3 回滚事务的方法

在这里插入图片描述

4.事务属性

4.1 只读属#性

4.1.1 简介

对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。但是如果你的方法中执行写操作,那么就会报错

4.1.2 设置方式

    @Transactional(readOnly = true)@Overridepublic void transfer(Integer fromId, Integer toId, Integer money) {// 1.转出账户扣款accountDao.updateAccountMoney(fromId, -money);// 测试报错是否会回滚
//        int num = 10 / 0;// 2.转入账户收款accountDao.updateAccountMoney(toId, money);}

4.1.3 如果在设置了只读的事务中进行写操作

会抛出下面异常

Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

4.1.4 如果将@Transactinal注解放在类上

4.1.4.1 生效原则

如果一个类中每一个方法上都使用了@Transactional注解,那么就可以将@Transactional注解提取到类上。反过来说:@Transactional注解在类级别标记,会影响到类中的每一个方法。同时,类级别标记的@Transactional注解中设置的事务属性也会延续影响到方法执行时的事务属性。除非在方法上又设置了@Transactional注解。

对一个方法来说,离它最近的@Transactional注解中的事务属性设置生效。

4.1.4.2 用法举例

在类级别@Transactional注解中设置只读,这样类中所有的查询方法都不需要设置@Transactional注解了。因为对查询操作来说,其他属性通常不需要设置,所以使用公共设置即可。

然后在这个基础上,对增删改方法设置@Transactional注解 readOnly 属性为 false。

package com.atguigu.serivce.impl;import com.atguigu.dao.AccountDao;
import com.atguigu.serivce.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/*** read-only属性表示事务是否只读:默认值是false,如果设置为true 那么当前事务中只能做数据库的读操作,不能做写操作* 该属性的作用:可以对只读的数据库操作做一些优化*/
@Service
// 类上使用此注解 表示类中每个方法都会生效 如果方法上也设置了 则使用方法上的注解
@Transactional(readOnly = true)
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;@Transactional(readOnly = false)@Overridepublic void transfer(Integer fromId, Integer toId, Integer money) {// 1.转出账户扣款accountDao.updateAccountMoney(fromId, -money);// 测试报错是否会回滚
//        int num = 10 / 0;// 2.转入账户收款accountDao.updateAccountMoney(toId, money);}
}

PS:Spring 环境下很多场合都有类似设定,一个注解如果标记了类的每一个方法那么通常就可以提取到类级别。但是,如果不是类中的所有方法都需要用到事务,则绝不允许将@Transaction注解放在类上

4.2 超时属性

4.2.1 简介

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。

此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。

概括来说就是一句话:超时回滚,释放资源。

4.2.2 设置方式

    // timeout 单位:秒  超过指定的时间没响应 则抛出异常@Transactional(readOnly = false,timeout = 3)@Overridepublic void transfer(Integer fromId, Integer toId, Integer money) {// 1.转出账户扣款accountDao.updateAccountMoney(fromId, -money);// 测试报错是否会回滚
//        int num = 10 / 0;// 2.转入账户收款accountDao.updateAccountMoney(toId, money);}

4.2.3 模拟超时

    // timeout 单位:秒  超过指定的时间没响应 则抛出异常@Transactional(readOnly = false,timeout = 3)@Overridepublic void transfer(Integer fromId, Integer toId, Integer money) {// 1.转出账户扣款accountDao.updateAccountMoney(fromId, -money);// 测试报错是否会回滚
//        int num = 10 / 0;// 模拟超时 try {Thread.sleep(3500);} catch (InterruptedException e) {e.printStackTrace();}// 2.转入账户收款accountDao.updateAccountMoney(toId, money);}

4.2.4 执行效果:

org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Thu Nov 17 09:55:44 CST 2022

4.3 回滚和不回滚的异常属性

4.3.1 默认情况

默认只针对运行时异常回滚,编译时异常不回滚。情景模拟代码如下:

    // timeout 单位:秒  超过指定的时间没响应 则抛出异常@Transactional(readOnly = false,timeout = 3)@Overridepublic void transfer(Integer fromId, Integer toId, Integer money) throws ClassNotFoundException {// 1.转出账户扣款accountDao.updateAccountMoney(fromId, -money);// 测试报错是否会回滚
//        int num = 10 / 0;// 什么是运行时异常:不需要在编译时处理的异常// 什么是编译时异常:需要在编译时就进行处理的异常// 默认情况时遇到运行时异常才回滚Class.forName("1eu1j1");// 2.转入账户收款accountDao.updateAccountMoney(toId, money);}

运行后抛出异常并没有事务回滚

在这里插入图片描述

在这里插入图片描述

4.3.2 设置回滚的异常

  • rollbackFor属性:需要设置一个Class类型的对象
  • rollbackForClassName属性:需要设置一个字符串类型的全类名
@Transactional(rollbackFor = Exception.class)

4.3.3 设置不回滚的异常

在默认设置和已有设置的基础上,再指定一个异常类型,碰到它不回滚。

@Transactional(noRollbackFor = FileNotFoundException.class
)

4.3.4 如果回滚和不回滚异常同时设置

4.3.4.1 当两者范围不同

不管是哪个设置范围大,都是在大范围内在排除小范围的设定。例如:

  • rollbackFor = Exception.class
  • noRollbackFor = FileNotFoundException.class

意思是除了 FileNotFoundException 之外,其他所有 Exception 范围的异常都回滚;但是碰到 FileNotFoundException 不回滚。

4.3.4.2 当两者范围相同(傻子才会这样去设置)
  • noRollbackFor = FileNotFoundException.class
  • rollbackFor = FileNotFoundException.class

此时 Spring 采纳了 rollbackFor 属性的设定:遇到 FileNotFoundException 异常会回滚。

4.4 事务隔离级别属性

4.4.1 回顾事务的隔离级别

级别名字隔离级别脏读不可重复读幻读数据库默认隔离级别
1读未提交read uncommitted
2读已提交read committedOracle
3可重复读repeatable readMySQL
4串行化serializable最高的隔离级别

4.4.2 设置方式

在 @Transactional 注解中使用 isolation 属性设置事务的隔离级别。 取值使用 org.springframework.transaction.annotation.Isolation 枚举类提供的数值。

@Transactional(isolation = Isolation.READ_UNCOMMITTED)

isolation :
(1). READ_UNCOMMITTED : 读未提交,在这种隔离级别下脏读、不可重复读、幻读都可能发生
(2). READ_COMMITTED : 读已提交,Oracle数据库的默认隔离级别,在这种隔离级别下不可能发生脏读
(3). REPEATABLE_READ : 可重复读,MySQL数据库的默认隔离级别,在这种隔离级别下不可能发生脏读、不可重复读; 这种隔离级别其实是使用了行锁
(4). SERIALIZABLE : 串行化,在这种隔离级别下不可能发生脏读、不可重复读、幻读; 这种隔离级别使用表锁

事务并行性问题:

  1. 脏读: 一个事务读取到了另一个事务未提交的数据,并且另一个事务最终没有提交
  2. 不可重复读: 一个事务中多次读取到的数据的内容不一致(原因是当前事务执行的时候,其它的事务使用UPDATE操作修改了数据)
  3. 幻读: 一个事务中多次读取到的数据的行数不一致(原因是当前事务执行的时候,其它事务使用了INSERT、DELETE操作新增、删除了记录)

4.5 事务传播行为属性

4.5.1 事务传播行为要研究的问题

事务的传播行为要研究的是是当两个方法嵌套执行的时候,外层方法的事务能否传播到内层方法以及怎么传播到内层方法

4.5.2 propagation属性

4.5.2.1 默认值

@Transactional 注解通过 propagation 属性设置事务的传播行为。它的默认值是:

Propagation propagation() default Propagation.REQUIRED;
4.5.2.2 可选值说明

propagation 属性的可选值由 org.springframework.transaction.annotation.Propagation 枚举类提供:

名称含义
REQUIRES_NEW如果当前存在事务(外层方法开启了事务),那么我就先将当前事务暂停,然后我自己新建事务执行,我的事务执行完之后再恢复当前事务,如果当前不存在事务(外层方法没有开启事务),那么我就新建事务执行
REQUIRED 默认值如果当前存在事务(外层方法开启了事务),那么我就加入外层方法的事务一起执行,如果当前不存在事务(外层方法没有开启事务),那么我就自己新建事务执行
SUPPORTS如果当前存在事务(外层方法开启了事务),那么我就加入到外层方法的事务中一起执行,如果当前不存在事务(外层方法没有开启事务),那么我自己就以非事务方式运行
MANDATORY如果当前存在事务(外层方法开启了事务),那么我就加入到外层方法的事务中一起执行,如果当前不存在事务(外层方法没有开启事务),那么我就报错(也可以理解为强制外层方法必须有事务)
NOT_SUPPORTED如果当前存在事务(外层方法开启了事务),那么我就先将当前事务暂停,我自己以非事务方式执行,我执行完之后再恢复当前事务, 如果外层方法不存在事务(外层方法没有开启事务),那么我就以非事务方式执行
NEVER如果当前存在事务(外层方法开启了事务),那么我就直接报错, 如果当前不存在事务(外层方法没有开启事务),那么我就以非事务方式执行

4.5.3 测试事务的传播行为

4.5.3.1 在业务层新建一个接口OuterService
package com.atguigu.service;public interface OuterService {void outerMethod();
}
4.5.3.2 新建一个实现类OuterServiceImpl
package com.atguigu.service.impl;import com.atguigu.dao.AccountDao;
import com.atguigu.pojo.Account;
import com.atguigu.service.AccountService;
import com.atguigu.service.OuterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class OuterServiceImpl implements OuterService {@Autowiredprivate AccountService accountService;@Autowiredprivate AccountDao accountDao;@Transactional@Overridepublic void outerMethod() {// 1.新增一条数据accountDao.add(new Account(3, "ww", 1000));// 2.调用AccountService的转账方法accountService.transfer(1, 2, 500);
//        int i = 10/ 0;// 3.修改id为3的用户的金额,给其加600accountDao.updateAccountMoney(3, 600);}
}
4.5.3.3 内层方法代码
package com.atguigu.serivce.impl;import com.atguigu.dao.AccountDao;
import com.atguigu.serivce.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;/*** read-only属性表示事务是否只读:默认值是false,如果设置为true 那么当前事务中只能做数据库的读操作,不能做写操作* 该属性的作用:可以对只读的数据库操作做一些优化*/
@Service
// 类上使用此注解 表示类中每个方法都会生效 如果方法上也设置了 则使用方法上的注解
@Transactional(readOnly = true)
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;// timeout 单位:秒  超过指定的时间没响应 则抛出异常@Transactional(readOnly = false,timeout = 3,propagation = Propagation.REQUIRED)@Overridepublic void transfer(Integer fromId, Integer toId, Integer money) throws ClassNotFoundException {// 1.转出账户扣款accountDao.updateAccountMoney(fromId, -money);// 测试报错是否会回滚int num = 10 / 0;// 什么是运行时异常:不需要在编译时处理的异常// 什么是编译时异常:需要在编译时就进行处理的异常// 默认情况时遇到运行时异常才回滚Class.forName("1eu1j1");// 2.转入账户收款accountDao.updateAccountMoney(toId, money);}
}
4.5.3.4 测试方法
package com.atguigu;import com.atguigu.controller.AccountController;
import com.atguigu.serivce.OuterService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class TestTransaction {@Autowiredprivate AccountController accountController;@Autowiredprivate OuterService outerService;@Testpublic void testTransfer() throws ClassNotFoundException {accountController.transfer(1, 2, 500);}@Testpublic void testPropagation(){outerService.outerMethod();}
}
4.5.3.5 测试REQUIRED模式

在这里插入图片描述

效果:内层方法A、内层方法B所作的修改没有生效,总事务回滚了

内部方法报错 总事物回滚

新增的数据也进行回滚了,可以通过日志进行查看

在这里插入图片描述

4.5.3.6 测试REQUIRES_NEW模式

修改AccountServiceImpl内层方法

    // timeout 单位:秒  超过指定的时间没响应 则抛出异常@Transactional(readOnly = false,timeout = 3,propagation = Propagation.REQUIRES_NEW)@Overridepublic void transfer(Integer fromId, Integer toId, Integer money){// 1.转出账户扣款accountDao.updateAccountMoney(fromId, -money);// 测试报错是否会回滚int num = 10 / 0;// 2.转入账户收款accountDao.updateAccountMoney(toId, money);}

效果:内层方法报错了,内层和外层的事务都回滚了

在这里插入图片描述
在这里插入图片描述

如果内层方法没报错,外层方法报错了那么内层事物会提交 外层事务回滚

内层方法代码

@Service
// 类上使用此注解 表示类中每个方法都会生效 如果方法上也设置了 则使用方法上的注解
@Transactional(readOnly = true)
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;// timeout 单位:秒  超过指定的时间没响应 则抛出异常@Transactional(readOnly = false,timeout = 3,propagation = Propagation.REQUIRES_NEW)@Overridepublic void transfer(Integer fromId, Integer toId, Integer money){// 1.转出账户扣款accountDao.updateAccountMoney(fromId, -money);// 测试报错是否会回滚
//        int num = 10 / 0;// 2.转入账户收款accountDao.updateAccountMoney(toId, money);}
}

外层方法代码

package com.atguigu.serivce.impl;import com.atguigu.dao.AccountDao;
import com.atguigu.pojo.Account;
import com.atguigu.serivce.AccountService;
import com.atguigu.serivce.OuterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class OuterServiceImpl implements OuterService {@Autowiredprivate AccountService accountService;@Autowiredprivate AccountDao accountDao;@Transactional@Overridepublic void outerMethod() {// 1.新增一条数据accountDao.add(new Account(3, "ww", 1000));// 2.调用AccountService的转账方法accountService.transfer(1, 2, 500);int num = 10 / 0;// 3.修改id为3的用户的金额,给其加600accountDao.updateAccountMoney(3, 600);}
}

测试效果 内部方法执行的转账成功了

在这里插入图片描述
在这里插入图片描述

4.5.3.7 测试SUPPORTS模式

修改内层方法为SUPPORTS模式

   @Transactional(readOnly = false,timeout = 3,propagation = Propagation.SUPPORTS)@Overridepublic void transfer(Integer fromId, Integer toId, Integer money){// 1.转出账户扣款accountDao.updateAccountMoney(fromId, -money);// 测试报错是否会回滚int num = 10 / 0;// 2.转入账户收款accountDao.updateAccountMoney(toId, money);}
}

执行测试方法 外层方法有事务 会加入到外层事务中 报错后事务会回滚

在这里插入图片描述
在这里插入图片描述

外层方法去掉事务后 再进行测试 内部方法报错后 并没有进行回滚(以非事务方式执行)

在这里插入图片描述

在这里插入图片描述

4.5.3.8 事务传播性总结

propagation : 事务的传播性,它是研究方法嵌套执行的时候,内层方法是否会共用外层方法的事务
事务传播性的取值:
(1). REQUIRED : 如果当前存在事务,则加入到当前事务中一起执行,如果当前不存在事务则新建事务执行
(2). SUPPORTS : 如果当前存在事务,则加入当前事务中一起执行,如果当前不存在事务则以非事务方式执行,一般用于只读事务
(3). MANDATORY : 如果当前存在事务,则加入当前事务中一起执行,如果当前不存在事务则报错
(4). REQUIRES_NEW : 新创建事务执行,如果外层方法有事务先将外层方法的事务暂停,等内层方法事务执行完毕之后再恢复外层方法的事务
(5). NOT_SUPPORTED : 以非事务方式执行,如果外层方法有事务则先将外层方法事务暂停,等我执行完之后再恢复外层方法的事务
(6). NEVER : 以非事务方式执行,如果外层方法有事务则报错

事务传播性总结:
(1). 如果一个方法里面只有读操作,一般情况下会设置其事务传播性为SUPPORTS,并且可以设置readOnly为true
(2). 如果一个方法里面有写操作,一般情况下会设置其事务的传播性为REQUIRED
(3). 如果一个方法里面有写操作,并且当前方法执行没有异常就一定要提交事务(不受外层方法影响),此时使用REQUIRES_NEW

5.基于XML配置声明式事务

5.1 加入依赖

相比于基于注解的声明式事务,基于 XML 的声明式事务需要一个额外的依赖:

org.springframeworkspring-aspects5.3.1

5.2 迁移代码

将上一个基于注解的 module 中的代码转移到新module。去掉 @Transactional 注解。

5.3 修改spring配置文件

去掉 tx:annotation-driven 标签,然后加入下面的配置:






5.4 注意

即使需要事务功能的目标方法已经被切入点表达式涵盖到了,但是如果没有给它配置事务属性,那么这个方法就还是没有事务。所以事务属性必须配置。

6.spring5的新特性

6.1 JSP的概述

6.1.1 JCP

JCP(Java Community Process) 是一个由SUN公司发起的,开放的国际组织。主要由Java开发者以及被授权者组成,负责Java技术规范维护,Java技术发展和更新。

JCP官网地址:https://jcp.org/en/home/index

6.1.2 JSR

JSR 的全称是:Java Specification Request,意思是 Java 规范提案。谁向谁提案呢?任何人都可以向 JCP (Java Community Process) 提出新增一个标准化技术规范的正式请求。JSR已成为Java界的一个重要标准。登录 JCP 官网可以查看所有 JSR 标准。

6.2 JSR305的规范

JSR 305: Annotations for Software Defect Detection(提供了一系列的软件缺陷检测(数据校验)的注解)

This JSR will work to develop standard annotations (such as @NonNull) that can be applied to Java programs to assist tools that detect software defects.

主要功能:使用注解(例如@NonNull等等)协助开发者侦测软件缺陷。

Spring 从 5.0 版本开始支持了 JSR 305 规范中涉及到的相关注解。

package org.springframework.lang;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierNickname;
/*** A common Spring annotation to declare that annotated elements cannot be {@code null}.** 

Leverages JSR-305 meta-annotations to indicate nullability in Java to common* tools with JSR-305 support and used by Kotlin to infer nullability of Spring API.**

Should be used at parameter, return value, and field level. Method overrides should* repeat parent {@code @NonNull} annotations unless they behave differently.**

Use {@code @NonNullApi} (scope = parameters + return values) and/or {@code @NonNullFields}* (scope = fields) to set the default behavior to non-nullable in order to avoid annotating* your whole codebase with {@code @NonNull}.** @author Sebastien Deleuze* @author Juergen Hoeller* @since 5.0* @see NonNullApi* @see NonNullFields* @see Nullable*/ @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Nonnull @TypeQualifierNickname public @interface NonNull { }

6.3 相关注解

注解名称含义可标记位置
@Nullable可以为空@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@NonNull不应为空@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@NonNullFields在特定包下的字段不应为空@Target(ElementType.PACKAGE) @TypeQualifierDefault(ElementType.FIELD)
@NonNullApi参数和方法返回值不应为空@Target(ElementType.PACKAGE) @TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER})

6.4 整合junit5

6.4.1 导入依赖

在原有环境基础上增加依赖 替换junit的依赖

org.junit.jupiterjunit-jupiter-api5.7.0test

6.4.2 创建测试类

  • @ExtendWith(SpringExtension.class) 表示使用 Spring 提供的扩展功能。
  • @ContextConfiguration(value = {“classpath:spring-context.xml”}) 还是用来指定 Spring 配置文件位置,和整合 junit4 一样。
package com.atguigu;import com.atguigu.controller.AccountController;
import com.atguigu.serivce.OuterService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;@ExtendWith(SpringExtension.class)
@ContextConfiguration(value = "classpath:spring.xml")
public class TestTransaction {@Autowiredprivate AccountController accountController;@Autowiredprivate OuterService outerService;@Testpublic void testTransfer() throws ClassNotFoundException {accountController.transfer(1, 2, 500);}@Testpublic void testPropagation(){outerService.outerMethod();}
}

6.4.3 使用复合注解

@SpringJUnitConfig 注解综合了前面两个注解的功能,此时指定 Spring 配置文件位置即可。但是注意此时需要使用 locations 属性,不是 value 属性了。

package com.atguigu;import com.atguigu.controller.AccountController;
import com.atguigu.serivce.OuterService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;@SpringJUnitConfig(locations = "classpath:spring.xml")
public class TestTransaction {@Autowiredprivate AccountController accountController;@Autowiredprivate OuterService outerService;@Testpublic void testTransfer() throws ClassNotFoundException {accountController.transfer(1, 2, 500);}@Testpublic void testPropagation(){outerService.outerMethod();}
}

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...