今天根据具体的业务代码场景去分析spring事务的源码流程,其中事务传播属性还是PROPAGATION_REQUIRED
首先看下第一种业务场景,testTransaction1方法上加了@Transactionl注解,注解中调用了insertTransactionOne(),insertTransactionTwo()方法,这两个方法上面也都加了@Transactionl注解
首先看testTransaction1()方法,上节已经说了会创建数据库连接connection并与ThreadLocal绑定,此时事务状态newTransaction=true,表示这是新开启的事务。这些流程上节已经看过了,再看方法transactionOne.insertTransactionOne(),这个时候同样要去开启事务执行到了getTransaction方法中从ThreadLocal中获取数据库连接,此时就会判断isExistingTransaction方法,明显是存在的,就会去执行handleExistingTransaction方法
handleExistingTransaction方法的主要功能:
1、如果当前被调用的方法insertTransactionOne的事务传播属性为PROPAGATION_NESTED,那么就会将testTransaction1方法开启的事务挂起。
2、如果当前被调用的方法insertTransactionOne的事务传播属性为PROPAGATION_REQUIRED,那么就会将newTransaction属性设置为false,这个留意后面提交事务的时候有使用,然后就会去调用insertTransactionOne的原始方法,可以看到当前insertTransactionOne跟testTransaction1是在同一个事务中了。
当testTransaction1()方法执行完毕进行commit方法的时候,这里因为status.isNewTransaction为false,所以并不会执行testTransaction1方法的提交操作。
同理,执行完insertTransactionOne方法之后,继续执行insertTransactionTwo方法,流程跟insertTransactionOne相同,当这两个方法执行完毕的时候,那么就又回到了testTransaction1方法,此时他的newTransaction为true,那么就会执行doCommit方法将事务进行提交。
第二种基本场景,testTransaction2方法中,假如insertTransactionOne方法执行成功,insertTransactionTwo方法抛出异常
抛出异常之后会进到completeTransactionAfterThrowing方法,这里会
由于testTransaction2方法的isNewTransaction为false,所以不会执行回滚动作,但是将connectionHolder的rollbackOnly属性设置为了true。
insertTransaction2抛出异常后回到了testTransaction方法的执行逻辑,此时会再次进入到completeTransactionAfterThrowing方法,由于testTransaction的事务状态newTransaction为true,那么最终会执行con.rollback方法进行事务回滚操作。所以rollbackOnly属性是事务回滚操作中比较重要的属性
第三种基本场景,事务传播属性依然是PROPAGATION_REQUIRED;insertTransactionOne方法执行成功,但是insertTransactionTwo抛出异常,但是此时异常被捕获到,那么此时insertTransactionOne插入的数据会回滚吗?答案是肯定的。
关键属性其实还是rollbackOnly,insertTransactionTwo由于抛出异常了,会调用completeTransactionAfterThrowing方法将rollbackOnly更新为true,然后异常抛出,那么此时testTransaction3中的方法捕获到异常。
此时回到了testTransaction3方法的执行流程,且newTransaction属性为true,那么就会正常执行commitTransactionAfterReturning方法,这个时候判断rollbackOnly为true
由于testTransaction3的当前事务状态newTransaction为true,此时事务回滚。
第四种基本场景,事务传播属性依然是PROPAGATION_REQUIRED;这种场景是insertTransactionOne方法正常执行,insertTransactionTwo方法执行报错但是内部将异常进行捕获,并没有抛出来,此时的结论是insertTransactionOne方法事务提交,insertTransactionTwo方法没有插入数据。
原因就是insertTransactionTwo方法自己将事务给捕获了,没有进入到spring中的catch方法里面,那就不会执行到completeTransactionAfterThrowing方法,因此也就不会将rollbackonly设置为true,因此testTransaction4方法正常提交。
以上就是事务传播属性还是PROPAGATION_REQUIRED的主要流程。
接下来看另外一场常见的事务传播机制PROPAGATION_REQUIRED_NEW(新建事务,如果当前存在事务,把当前事务挂起)
在开发过程中,经常会出现一个方法调用另外一个方法,那么这里就涉及到了多种场景,比如a()调用b():
1. a()和b()方法中的所有sql需要在同一个事务中吗?
2. a()和b()方法需要单独的事务吗?
3. a()需要在事务中执行,b()还需要在事务中执行吗?
4. 等等情况...
所以,这就要求Spring事务能支持上面各种场景,这就是Spring事务传播机制的由来。那Spring事务PROPAGATION_REQUIRED_NEW传播机制是如何实现的呢?假如方法a()在一个事务中执行,a中调用了b方法,调用b()方法时需要新开一个事务执行:
1. 首先,代理对象执行a()方法前,先利用事务管理器新建一个数据库连接a
2. 将数据库连接a的autocommit改为false
3. 把数据库连接a设置到ThreadLocal中
4. 执行a()方法中的sql
5. 执行a()方法过程中,调用了b()方法(注意用代理对象调用b()方法)
i. 代理对象执行b()方法前,判断出来了当前线程中已经存在一个数据库连接a了,表示当前线
程其实已经拥有一个Spring事务了,则进行挂起
ii. 挂起就是把ThreadLocal中的数据库连接a从ThreadLocal中移除,并放入一个挂起资源对象
中
iii. 挂起完成后,再次利用事务管理器新建一个数据库连接b
iv. 将数据库连接b的autocommit改为false
v. 把数据库连接b设置到ThreadLocal中
vi. 执行b()方法中的sql
vii. b()方法正常执行完,则从ThreadLocal中拿到数据库连接b进行提交
viii. 提交之后会恢复所挂起的数据库连接a,这里的恢复,其实只是把在挂起资源对象中所保存
的数据库连接a再次设置到ThreadLocal中
6. a()方法正常执行完,则从ThreadLocal中拿到数据库连接a进行提交
这个过程中最为核心的是:在执行某个方法时,判断当前是否已经存在一个事务,就是判断当前线程的ThreadLocal中是否存在一个数据库连接对象,如果存在则表示已经存在一个事务了。
方法a第一次执行的时候,正常开启事务,流程与PROPAGATION_REQUIRED是一样的,关键流程是执行方法b()的时候,判断是存在数据库连接对象的、那么就会执行到handleExistingTransaction方法,做事务挂起操作。
suspend方法就是把ThreadLocal中的数据库连接从ThreadLocal中移除,并放入一个挂起资源对象 中,最终返回的suspendedResources就是持有了上个方法事务的连接,调用b方法这个时候就会重新调用startTransaction方法开启新的事务,也就是会创新新的连接。
b()方法开启新事务有两个关注的点:
1、newTransaction属性为true,就说明这是一个新的事务,当b方法执行到提交的时候就会真正提交;如果传播属性是PROPAGATION_REQUIRED,此时b方法这个属性应该为false,就表名方法b没有开启新事务,复用a方法的事务,最终提交由a方法提交
2、新创建的DefaultTransactionStatus方法的suspendedResources不为空,就是刚刚持有a()方法数据库连接的挂起资源对象;如果如果传播属性是PROPAGATION_REQUIRED,这个suspendedResources就是null,因为不会有挂起操作。这个对象在执行完b方法事务提交后,恢复a方法事务的时候需要用到,因此需要拿到a方法的连接。
b方法事务提交流程就跟PROPAGATION_REQUIRED也没什么区别,不过会有一个挂起事务恢复的操作,执行完b方法的事务提交之后,会走到cleanupAfterCompletion方法,由于当前事务的suspendedResources不为空,就会执行resume方法
这里会将挂起资源的数据库连接与当前线程ThreadLocal做一个重新绑定的操作,然后继续去执行a()方法的业务,如果此时a()方法执行失败,那么不会回滚b方法的事务。
Spring事务强制回滚操作:
正常情况下,a()调用b()方法时,如果b()方法抛了异常,但是在a()方法捕获了,那么a()的事务还是会正常提交的,但是有的时候,我们捕获异常可能仅仅只是不把异常信息返回给客户端,而是为了返回一些更友好的错误信息,而这个时候,我们还是希望事务能回滚的,那这个时候就得告诉Spring把当前事务回滚掉,其实利用的还是rollbackonly属性,做法就是:
最后一种事务传播属性PROPAGATION_NESTED(如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作),这种传播属性比较简单,其实就是利用数据库连接对象,在事务中设置一个savepoint,后续可以只回滚到某个savepoint。
标签:事务,执行,Spring,数据库,PROPAGATION,源码,解析,方法,连接 From: https://blog.csdn.net/qq_40228720/article/details/143156381