一、基本了解
点开源码可以看到如上属性,分别讲解,value和transactionManager属性是配置一个Spring的事务管理器,事务的打开,回滚,提交都是由事务管理器来完成的,事务管理器随着我们引用starter依赖包会自动引入,无需我们创建管理,事务管理器也有很多,根据我们具体操作而定,比如我们常用的mybatis/hibernate都有各自的事务管理器,当我们调用操作数据库的函数时候,其事务控制就由我们引入的依赖包附带的事务管理器来进行控制;timeout事务可以允许存在的时间戳,单位s;readOnly定义事务是否是只读事务(只读事务,指的是从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!主要是针对多次读取,相当于避免了可重复读以及幻读问题);rollbackFor/rollbackForClassName/noRoolbackFor/noRoolbackForClassName都是指定异常,这个异常就是@Transactional所在方法里面可能出现的异常,配置后就可以灵活的进行回滚以及不会滚控制;propagation传播行为,isolation隔离级别,接下来重点讲解。看@Target就知道可以放在类上接口上或者方法上,相对于接口肯定是推荐放在实现类上,因为放在接口上将使得你的类基于接口的代理时它才生效,也就是说只能JDK动态代理而不能CGLIB代理。隔离级别其实就不用多说了,具体可以参考数据库事务四种级别,以及需要重点关注的两类更新丢失覆盖、回滚,三类读问题脏读、不可重复读、幻读问题,具体可以参考:https://blog.csdn.net/deniro_li/article/details/83927739,接下来重点说下传播行为,所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。传播行为是方法之间调用 事务采取的策略问题,当一个方法调用另外一个方法时候可以让事务采取不同的策略工作比如新建事务或者挂起当前事务等,这就是事务的传播行为,举个例子,我们一次性清理购物车多种产品,具体到每种物品交易的时候对应各自的新建事务,各自控制各自的行为,某种物品交易失败不影响整个整个购物车清理行为以及其他物品的购买行为。事务的传播行为分为7种:接下来对7种隔离级别逐个进行验证,说下测试注意事项,新建两个service,互相独立,service1里面方法A,service2里面方法BC,千万不要一个service里面this.functionB()这样正好导致Spring AOP失效,@Transactional是基于Spring AOP的,影响测试。以下截图以及结论均经过本人测试验证!
1、REQUIRED:需要事务,默认传播行为,如果当前存在事务就沿用当前事务,如果不存在就新建一个事务运行子方法。
如上所示,调用functionA()有事务,另外一个serviceImpl中的BC方法没有事务,因此BC加入到当前A的事务中,出现异常直接回滚全部流程,整体就一个物理事务,因此当A自己抛出异常一样会回滚。当前指的是A方法,BC的上下文环境。所以这样操作结果是都没有插入,C抛出异常A捕获后事务回滚,需要说下默认如果不设置rollbackFor则不回滚,有异常就抛出跟普通方法一样,这样就是ABC三条都插入成功并抛出异常,设置后就是ABC都回滚并抛出异常。
同样的如上图,无论BC方法有无事务,从A方法来说都会传播给BC使得ABC共同使用一个事务,因此只要A上配置了REQUIRED就一个事务,出问题就回滚。
如上图如果当前不存在事务,则新建一个事务运行子方法,父类出现异常不回滚。
如上图,子方法出现异常只回滚了子方法的事务,父类就没有事务所以没有回滚一说。
2、REQUIRES_NEW:无论当前事务是否存在,都会创建新事务运行方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立。
如上图所示,A默认隔离事务,BC是REQUIRE_NEW,无论当前A是否存在事务,都会创建新事务运行BC方法,也就是说ABC各自的事务都是完全隔离的,所以当C抛出异常C回退,A继续捕获也跟着回退,从而结果只有B插入成功。
为了验证以上是否正确我们继续模拟,AC不变,B传播隔离级别改成REQUIRED按照如上设置我们推理,AB共用一个事务,C独立管理自己事务,测试结果果真都没有插入。
3、NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点,内部事务回滚不会对外部事务造成影响。
如上图,C方法会沿用A的事务,B自己独立一个事务,当C抛出异常AC回退,B自己成功。这么一看跟REQUIRED很相似,那么区别呢?
如上图会抛出异常org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only,因为AC共用1个物理事务,AC共用的事务已经回退了,但是A还想正常执行,抛出了异常。执行结果也是只有B自己插入成功。
但是如果改成NESTED就可以发现AB执行成功。C自己回退三个独立事务。
4、SUPOORTS:支持事务,如果当前存在事务,就沿用当前事务,如果不存在,则继续采用无事务的方式运行子方法。
对于C来说当前存在事务,沿用A的事务,所以AC一个事务,B一个事务,当C抛出异常,只有B插入成功。如果当前不存在事务,如下图
将A上的事务注释掉,则采用无事务运行子方法,也就是说ABC都插入成功。
5、MANDATORY:必须使用事务,如果当前没有事务,则会抛出异常,如果存在事务,就沿用当前事务。
如上图所示,当前不存在事务,首先会执行AB然后抛出异常:org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'。但是由于A没有事务不回滚,B则是自己独立的事务也不会回滚,而执行到C就抛出异常了,所以AB插入成功。
6、NOT_SUPPORTED:不支持事务,当前存在事务时,将挂起事务,运行方法。
B独立事务一定会成功,C存在事务被挂起不生效所以不回滚,抛出异常后A捕获进行回滚
7、NEVER:不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行
如上图,A方法设置此隔离级别,对于A来说当前环境不存在事务,按照无事务机制运行,因此插入ABC,如果放到C方法上,如下
则B执行成功,执行到C的时候抛出:org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never' 异常,然后A跟着回退。只有B插入成功。
注意事项:其实上面做测试样例的时候也说过,如果我们在同一个类中一个方法调用另外一个方法,会导致@Transactional注解失效,因为其实现原理是Spring AOP,而AOP则是有接口就JDK动态代理,在自调用的时候是类本身的调用而不是代理对象去调用,那么就不会产生AOP,就无法织入业务逻辑。