问题
spring的事务处理为我们省掉了很多操作,如今只需要简单的配置一下就可以完成对应的事务操作。不过停留在会用的层面上还是缺少一些遇见问题时处理的能力。下面我要总结一下spring事务处理时的设计中,传播级别到底是如何工作的,比如
- REQUIRED为什么在try中catch住异常
外层事务
一样会回滚? - REQUIRES_NEW是如何开启新事物,并与
外层事务
独立开来? - NESTED是如何做到
子事务
不影响外层事务?外层事务会影响子事务?
名词
外层事务:事务A中调用了事务B,那么事务A就是B的外层事务。
新事务:事务A中调用事务B,事务B的传播级别为REQUIRES_NEW,那么事务B就是新事务。
子事务:事务A中调用事务B,事务B的传播级别为NESTED,那么事务B就是新事务。
探究
先把这里org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
作为事务处理的入口,来看一下里面的行为。
前三行是一些配置信息与调用的方法信息,不多讲。
这里的关键点在于if里面的内容:
1.获取TransactionInfo
信息,可能会新建事务。createTransactionIfNecessary
2.执行事务代码invocation.proceedWithInvocation();
3.发生异常的时候的处理completeTransactionAfterThrowing(txInfo, ex);
4.无论有无异常,都会清理当前的事务信息。cleanupTransactionInfo(txInfo);
5.正常执行后的提交动作。commitTransactionAfterReturning(txInfo);
这里的提交并不保证完成提交,是问题1,3的关键。
先介绍几个关键的元信息类:
TransactionDefinition
:就是被@Transaction中的一些属性信息,包含传播级别,隔离级别,超时时间,只读设置,名字。
TransactionAttribute
:继承了TransactionDefinition,包含了qualifier,rollbackOn异常回滚的支持信息。
DefaultTransactionStatus
:
transaction:事务对象,每一个事务方法都可能有自己独立的对象,也可能公用一个。
newTransaction:是否是新事务,用来影响事务的提交和回滚。
newSynchronization: 新的同步器?没找到一个很好的解释,用来控制当前线程与事务属性的关系。
TransactionSynchronizationManager
就是这个关系的管理器。readOnly: 只读属性,这里不关心。
debug:就是一个debug的开启缓存,省去二次计算。
suspendedResources:挂起的资源,比如REQUIRES_NEW需要挂起外层事务。
TransactionInfo:事务的信息概括,包含上面的TransactionAttribute,TransactionStatus,和事务管理器对象,方法信息,重要的是oldTransactionInfo外层事务信息,在事务结束后会恢复执行外层的事务逻辑。
回到方法TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
在449行用了一个委托的事务属性对象来描述TransactionAttribute
。就是关于事务方法的描述信息。
再来看461的getTransaction
,这个过程就是对不同的传播级别的处理方式。我们这里只关注DefaultTransactionStatus的变化。
可见这里处理的有存在事务与不存在事务的两种处理流程。
isExistingTransaction
通过实现可以看到就是数据库连接是否打开了事务。
先看在新建事务时的过程:
- 358行,传播级别为PROPAGATION_MANDATORY时,抛出异常,这就是这个传播级别必须要在事务方法中调用的原因。
- 362行,对于REQUIRED,REQUIRES_NEW,NESTED级别的处理。重点关注
DefaultTransactionStatus
里面的newTransaction和newSynchronization
。
370行,在默认情况下newSynchronization为true,因为getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS
371行,newTransactionStatus方法中,再用
boolean actualNewSynchronization = newSynchronization &&!TransactionSynchronizationManager.isSynchronizationActive();
来决定最终的newSynchronization。
TransactionSynchronizationManager.isSynchronizationActive()
就是获取当前现成的事务同步器的激活状态。如果已经激活,就不用重复激活了。
由此可见,最终的newSynchronization是和当前的同步器激活状态关系的。只要是已经激活,那么就是false了。
373行,doBegin就是开启了事务连接的事务状态。
374行,prepareSynchronization准备同步器的初始化。进入方法里面可以看见是设置了当前线程的:
1.激活状态。2.隔离级别。3.只读。4.事务名称。5.空的同步器集合(TransactionSynchronization)。
这里我把几种级别的各个参数流程列一下,以便讲解:
image.png
1.REQUIRED:
在没有外层事务时,它需要一个事务管理器transaction,此时的事务同步器还没有激活,因此需要激活当前线程的事务同步器即actualNewSynchonization为true,同时调用了doBegin来让具体的实现打开线程状态。最后准备并初始化线程同步器的状态prepareSynchronization,即设置了当前线程事务的信息,并激活了同步器状态initSynchronization
。
在有外层事务时,它不需要挂起事务资源,因为要加入到当前的事务。事务同步器与外层用同一个。由于不会挂起资源,也就不会重置同步器的状态,也不需要重新激活同步器。这样就加入到当前的事务中。
之后在调用事务方法的时候,如果出现了异常,即使被外层捕获到不抛出,也会使外层事务回滚。这里我们就要看一下completeTransactionAfterThrowing这个方法里面的东西。
很简单,在事务有效的时候,符合需要回滚的异常时,走rollback方法。否则继续commit操作。
再来深入一下rollback方法,里面调用了processRollback,
image.png
这里走的是第三个if分支,两个条件,1.TransactionStatus的rollbackOnly状态;2.全局的rollbackOnly状态isGlobalRollbackOnParticipationFailure,默认返回true,表示调用失败时候,需要全局回滚。之后进入到doSetRollbackOnly,其实就是让各自的实现去设置连接的rollbackOnly状态。
之后在外层事务提交的时候,具体看commit方法。
image.png
我们看见两个分支,
defStatus.isLocalRollbackOnly()只回滚本事务
和
!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly(),shouldCommitOnGlobalRollbackOnly表示全局事务在标记rollbackOnly时候是否需要继续提交,默认返回false,只有JTa的实现返回了True。defStatus.isGlobalRollbackOnly()是获取transaction的rollbackOnly标记,应该是标记在对应的连接状态上。
由上面发现已经将这个transaction标记了rollbackOnly,所以就转向了去处理回滚的操作。processRollback->doRollback。
描述有点多,总结一下就是:
1.REQUIRED参与到当前的事务,融合进来。
2.当它出现异常时,会标记当前的transaction管理器中连接的rollbackOnly状态,使得外层事务在提交的时候监测到这个标记,发现不可以继续提交,需要回滚。
2.REQUIRES_NEW:
在没有外层事务时,情况同REQUIRED,就是打开一个事务,激活同步器等操作。
在有外层事务时,它是需要挂起事务资源的,挂起资源时,会将同步器的状态恢复为初始状态,通知具体实现进行相应的挂起资源操作。这个时候此事务的同步器状态为true,所以会准备一个新的事务同步器状态,开启新事务doBegin。
同1理来看,这个新事物在processRollback中不会标记transaction管理器的rollbackOnly状态,只是触发了自己的doRollback操作,所以不会影响到外层事务的提交。
3.NESTED:
在没有外层事务时,情况同REQUIRED,就是打开一个事务,激活同步器等操作。
在有外层事务时,它不会挂起事务资源,这里讨论有支持savePoint的实现(除了Jta)。不会重置同步器,也就是说与外层事务共用一个同步器。与REQUIRED类似,与外层事务进行了融合。只不过在发生异常的时候,在processRollback走了status.hasSavepoint()分支,这样就优先进入了status.rollbackToHeldSavepoint()里面。
image.png
然后通过对应的实现通知连接来回滚到某个savePoint,并且释放掉。
这样就完成了单独的回滚操作,并不会向REQUIRED那样影响到外层的提交。
以上我们三个问题解答完了。
4.MANDATORY:
没有外层事务时抛出异常,必须要在事务里面执行。
有外层事务时,像REQUIRED那样融合进去。
5.SUPPORTS:
没有外层事务时,不会使用transaction管理器,也就不会形成事务操作。
有外层事务时,像REQUIRED那样融合进去。
6.NOT_SUPPORTED:
没有外层事务时,以非事务方式运行。
有外层事务时,把外层事务挂起,但是不会为自己新建事务,以非事务状态运行。
7.NEVER:
没有外层事务时,以非事务方式运行。
有外层事务时,抛出一场。
总结:
以上就是把所有传播级别的具体流程简单分析了一下,让我们知道了这7中级别是如何工作的,也解决了REQUIRED,REQUIRES_NEW和NESTED里面对于异常是如何处理的。
1.REQUIRED在处理异常时是给对应的连接设置了rollbackOnly状态,来影响外层事务的提交的。外层事务发现了rollbackOnly为true,就会转向到回滚流程。
2.REQUIRES_NEW是一个单独的事务操作,回滚完自己的事务不会影响其他。
3.NESTED是融合在外层事务中的,只不过这里支持了savePoint的操作,使得子事务在回滚时会指定到一个savePoint上,不会影响外层事务。因为与外层事务融合了,所以外层事务会影响到子事务。
网友评论