目录
1.@Transcational详解
1.1 rollbackFor
2事务隔离级别
2.1MySQL事务隔离级别回顾
2.2Spring事务隔离级别
3.Spring事务传播机制
3.1什么是事务传播机制
3.2事务的传播机制有哪些
3.3 Spring 事务传播机制使用和各种场景演示
3.3.1 REQUIRED(加入事务)
3.3.2 REQUIRES_NEW(新建事务)
3.3.3 NEVER(不支持当前事务,抛异常)
3.3.4 NESTED(嵌套事务)
3.3.5NESTED和REQUIRED 有什么区别?
4.总结
承接上文Spring事务及其传播机制(一)
1.@Transcational详解
通过之前的代码,我们学习了@Transactional的基本使用.接下来我们学习@Transactional注解的使用细节.
我们主要学习@Transactional注解当中的三个常见属性:
1.rollbackFor:异常回滚属性.指定能够触发事务回滚的异常类型.可以指定多个异常类型
2.Isolation:事务的隔离级别.默认值为Isolation.DEFAULT
3.propagation:事务的传播机制.默认值为Propagation.REQUIRED
1.1 rollbackFor
@Transactional默认只在遇到运行时异常和Error时才会回滚,非运行时异常不回滚.即Exception的子类中,除了RuntimeException及其子类.
我们上面为了演示事务回滚, ⼿动设置了程序异常
int a = 10/0;
接下来我们把异常改为如下代码
@Transactional@RequestMapping("/r2")public String r2(String name,String password) throws IOException { //⽤户注册userService.registryUser(name,password);log.info("⽤户数据插⼊成功");if (true){throw new IOException();}return "r2";}
运行程序:
发现虽然程序抛出了异常, 但是事务依然进行了提交.
如果我们需要所有异常都回滚,需要来配置@Transactional注解当中的rollbackFor属性,通过rollbackFor这个属性指定出现何种异常类型时事务进行回滚.
@Transactional(rollbackFor = Exception.class) @RequestMapping("/r2")public String r2(String name,String password) throws IOException { //⽤户注册userService.registryUser(name,password);log.info("⽤户数据插⼊成功");if (true){throw new IOException();}return "r2";}
此次运行程序抛出异常后事务将会进行回滚,IOException为Exception的子类
结论:
在Spring的事务管理中 ,默认只在遇到运⾏时异常RuntimeException和Error时才会回滚.
如果需要回滚指定类型的异常, 可以通过rollbackFor属性来指定.
2事务隔离级别
2.1MySQL事务隔离级别回顾
SQL 标准定义了四种隔离级别, MySQL 全都支持. 这四种隔离级别分别是:
1.读未提交(READ UNCOMMITTED):读未提交,也叫未提交读.该隔离级别的事务可以看到其他事务中未提交的数据
因为其他事务未提交的数据可能会发生回滚,但是该隔离级别却可以读到,我们把该级别读到的数据称之为脏数据,这个问题称之为脏读.
2.读提交(READ COMMITTED):读已提交,也叫提交读.该隔离级别的事务能读取到已经提交事务的数据
该隔离级别不会有脏读的问题.但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同SQL查询可能会得到不同的结果,这种现象叫做不可重复读
3.可重复读(REPEATABLE READ):事务不会读到其他事务对已有数据的修改,即使其他事务已提交.也就可以确保同一事务多次查询的结果一致,但是其他事务新插入的数据,是可以感知到的.这也就引发了幻读问题,可重复读,是MySQL的默认事务隔离级别。
比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因).明明在事务中查询不到这条信息,但自己就是插入不进去,这个现象叫幻读.
4.串行化(SERIALIZABLE):序列化,事务最高隔离级别.它会强制事务排序,使之不会发生冲突,从而解决了脏读,不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多
2.2Spring事务隔离级别
Spring中事务隔离级别有5种:
1.Isolation.DEFAULT:以连接的数据库的事务隔离级别为主.
2.Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准中READ UNCOMMITTED
3.Isolation.READ_COMMITTED:读已提交,对应SQL标准中READ COMMITTED
4.Isolation.REPEATABLE_READ:可重复读,对应SQL标准中REPEATABLE READ
5.Isolation.SERIALIZABLE:串行化,对应SQL标准中SERIALIZABLE
public enum Isolation {DEFAULT(-1),READ_UNCOMMITTED(1),READ_COMMITTED(2),REPEATABLE_READ(4),SERIALIZABLE(8); private final int value; private Isolation(int value) {this.value = value;}public int value() {return this.value;}}
Spring中事务隔离级别可以通过@Transactional中的isolation属性进行设置
@Transactional(isolation = Isolation.READ_COMMITTED)@RequestMapping("/r3")public String r3(String name,String password) throws IOException { //... 代码省略return "r3"; }
3.Spring事务传播机制
3.1什么是事务传播机制
事务传播机制就是:多个事务方法存在调用关系时,事务是如何在这些方法间进行传播的.
比如有两个方法A,B都被@Transactional修饰,A方法调用B方法
A方法运行时,会开启一个事务.当A调用B时,B方法本身也有事务,此时B方法运行时,是加入A的事务,是创建一个新的事务呢?
这个就涉及到了事务的传播机制。
比如公司流程管理执行任务之前,需要先写执行文档,任务执行结束,再写总结汇报
此时A部门有一项工作,需要B部门的支援,此时B部门是直接使用A部门的文档,还是新建一个文档呢?
事务隔离级别解决的是多个事务同时调用一个数据库的问题
而事务传播机制解决的是一个事务在多个节点(方法)中传递的问题
3.2事务的传播机制有哪些
@Transactional注解支持事务传播机制的设置,通过propagation属性来指定传播行为.
Spring事务传播机制有以下7种:
1.Propagation.REQUIRED:默认的事务传播级别.如果当前存在事务,则加入该事务.如果当前没有事务,则创建一个新的事务.
2.Propagation.SUPPORTS:如果当前存在事务,则加入该事务.如果当前没有事务,则以非事务的方式继续运行.
3.Propagation.MANDATORY :强制性.如果当前存在事务,则加入该事务.如果当前没有事务,则抛出异常.
4.Propagation.REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起.也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰.
5.Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起(不用).
6.Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
7.Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行.如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED.
public enum Propagation {REQUIRED(0),SUPPORTS(1),MANDATORY(2),REQUIRES_NEW(3),NOT_SUPPORTED(4),NEVER(5),NESTED(6); private final int value; private Propagation(int value) {this.value = value;}public int value() {return this.value;}}
3.3 Spring 事务传播机制使用和各种场景演示
对于以上事务传播机制,我们重点关注以下两个就可以了:
1.REQUIRED(默认值)
2.REQUIRES_NEW
3.3.1 REQUIRED(加入事务)
看下面代码实现:
1.用户注册,插入一条数据
2.记录操作日志,插入一条数据(出现异常)
观察propagation =Propagation.REQUIRED的执行结果
@RequestMapping("/propaga") @RestControllerpublic class PropagationController { @Autowiredprivate UserService userService; @Autowiredprivate LogService logService;@Transactional(propagation = Propagation.REQUIRED)@RequestMapping("/p1")public String r3(String name,String password){ //⽤户注册userService.registryUser(name,password); //记录操作⽇志logService.insertLog(name,"⽤户注册");return "r3"; }}
对应的UserService和LogService都添加@Transactional(propagation =Propagation.REQUIRED)
@Slf4j@Servicepublic class UserService { @Autowiredprivate UserInfoMapper userInfoMapper;@Transactional(propagation = Propagation.REQUIRED)public void registryUser(String name,String password){//插⼊⽤户信息userInfoMapper.insert(name,password);}}
@Slf4j@Servicepublic class LogService { @Autowiredprivate LogInfoMapper logInfoMapper; @Transactional(propagation = Propagation.REQUIRED)public void insertLog(String name,String op){int a=10/0; //记录⽤户操作logInfoMapper.insertLog(name,"⽤户注册"); }}
运行程序,发现数据库没有插入任何数据流程描述:
1.p1方法开始事务
2.用户注册,插入一条数据(执行成功)(和p1使用同一个事务)
3.记录操作日志,插入一条数据(出现异常,执行失败)(和p1使用同一个事务)
4.因为步骤3出现异常,事务回滚.步骤2和3使用同一个事务,所以步骤2的数据也回滚了.
3.3.2 REQUIRES_NEW(新建事务)
将上述UserService和LogService中相关方法事务传播机制改为Propagation.REQUIRES_NEW
@Servicepublic class UserService { @Autowiredprivate UserInfoMapper userInfoMapper; @Transactional(propagation = Propagation.REQUIRES_NEW)public void registryUser(String name,String password){//插⼊⽤⼾信息userInfoMapper.insert(name,password);}}
@Servicepublic class LogService { @Autowiredprivate LogInfoMapper logInfoMapper; @Transactional(propagation = Propagation.REQUIRES_NEW)public void insertLog(String name,String op){int a=10/0; //记录⽤⼾操作logInfoMapper.insertLog(name,"⽤户注册"); }}
运行程序,发现用户数据插入成功了,日志表数据插入失败.LogService方法中的事务不影响UserService中的事务.当我们不希望事务之间相互影响时,可以使用该传播行为.
3.3.3 NEVER(不支持当前事务,抛异常)
修改UserService中对应方法的事务传播机制为Propagation.NEVER
@Slf4j@Servicepublic class UserService { @Autowiredprivate UserInfoMapper userInfoMapper; @Transactional(propagation = Propagation.NEVER)public void registryUser(String name,String password){//插⼊⽤⼾信息userInfoMapper.insert(name,password);}}
程序执行报错, 没有数据插入
3.3.4 NESTED(嵌套事务)
将上述UserService和LogService中相关方法事务传播机制改为Propagation.NESTED
@Slf4j@Servicepublic class UserService { @Autowiredprivate UserInfoMapper userInfoMapper; @Transactional(propagation = Propagation.NESTED)public void registryUser(String name,String password){//插⼊⽤⼾信息userInfoMapper.insert(name,password);}}
@Servicepublic class LogService { @Autowiredprivate LogInfoMapper logInfoMapper; @Transactional(propagation = Propagation.NESTED)public void insertLog(String name,String op){int a=10/0; //记录⽤⼾操作logInfoMapper.insertLog(name,"⽤户注册"); }}
运行程序, 发现没有任何数据插入 .
流程描述:
1.Controller中p1方法开始事务
2.UserService用户注册,插入一条数据(嵌套p1事务)
3.LogService记录操作日志,插入一条数据(出现异常,执行失败)(嵌套p1事务,回滚当前事务,数
据添加失败)
4.由于是嵌套事务,LogService出现异常之后,往上找调用它的方法和事务,所以用户注册也失败了.
5.最终结果是两个数据都没有添加
p1事务可以认为是父事务,嵌套事务是子事务.父事务出现异常,子事务也会回滚,子事务出现异常,如果不进行处理,也会导致父事务回滚。
3.3.5NESTED和REQUIRED 有什么区别?
我们在LogService进行当前事务回滚,修改LogService代码如下:
@Servicepublic class LogService { @Autowiredprivate LogInfoMapper logInfoMapper; @Transactional(propagation = Propagation.REQUIRES_NEW)public void insertLog(String name,String op){try{int a=10/0; }catch(Exception e){ // ⼿动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); }//记录⽤⼾操作logInfoMapper.insertLog(name,"⽤户注册"); }}
重新运行程序,发现用户表数据添加成功,日志表添加失败.
LogService中的事务已经回滚,但是嵌套事务不会回滚嵌套之前的事务,就是说嵌套事务可以实现部分事务回滚.REQUIRED如果回滚就是回滚所有事物,不能实现部分事务回滚(属于同一事务)
整个事务如果全部执行成功,二者的结果是一样的.
如果事务一部分执行成功,REQUIRED加入事务会导致整个事务全部回滚.NESTED嵌套事务可以实现局部回滚,不会影响上一个方法中执行的结果.
嵌套事务之所以能够实现部分事务的回滚,是因为事务中有一个保存点(savepoint)的概念,嵌套事务进入之后相当于新建了一个保存点,而滚回时只回滚到当前保存点.
4.总结
1.Spring中使用事务,有两种方式:编程式事务(手动操作)和声明式事务.其中声明式事务使用较多,在方法上添加@Transactional就可以实现了
2.通过@Transactional(isolation =Isolation.SERIALIZABLE)设置事务的隔离级别.Spring中的事务隔离级别有5种
3.通过@Transactional(propagation =Propagation.REQUIRED)设置事务的传播机制,Spring中的事务传播级别有7种,重点关注REQUIRED(默认值)和REQUIRES_NEW