spring事务处理的设计与实现--传播级别的具体流程

问题

spring的事务处理为我们省掉了很多操作,如今只需要简单的配置一下就可以完成对应的事务操作。不过停留在会用的层面上还是缺少一些遇见问题时处理的能力。下面我要总结一下spring事务处理时的设计中,传播级别到底是如何工作的,比如

  1. REQUIRED为什么在try中catch住异常外层事务一样会回滚?
  2. REQUIRES_NEW是如何开启新事物,并与外层事务独立开来?
  3. NESTED是如何做到子事务不影响外层事务?外层事务会影响子事务?

名词

外层事务:事务A中调用了事务B,那么事务A就是B的外层事务。
新事务:事务A中调用事务B,事务B的传播级别为REQUIRES_NEW,那么事务B就是新事务。
子事务:事务A中调用事务B,事务B的传播级别为NESTED,那么事务B就是新事务。

探究

先把这里org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction作为事务处理的入口,来看一下里面的行为。

image.png

前三行是一些配置信息与调用的方法信息,不多讲。

这里的关键点在于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

image.png

transaction:事务对象,每一个事务方法都可能有自己独立的对象,也可能公用一个。
newTransaction:是否是新事务,用来影响事务的提交和回滚。
newSynchronization: 新的同步器?没找到一个很好的解释,用来控制当前线程与事务属性的关系。TransactionSynchronizationManager就是这个关系的管理器。
readOnly: 只读属性,这里不关心。
debug:就是一个debug的开启缓存,省去二次计算。
suspendedResources:挂起的资源,比如REQUIRES_NEW需要挂起外层事务。

TransactionInfo:事务的信息概括,包含上面的TransactionAttribute,TransactionStatus,和事务管理器对象,方法信息,重要的是oldTransactionInfo外层事务信息,在事务结束后会恢复执行外层的事务逻辑

回到方法TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

image.png

在449行用了一个委托的事务属性对象来描述TransactionAttribute。就是关于事务方法的描述信息。
再来看461的getTransaction,这个过程就是对不同的传播级别的处理方式。我们这里只关注DefaultTransactionStatus的变化。

image.png

可见这里处理的有存在事务与不存在事务的两种处理流程。
isExistingTransaction通过实现可以看到就是数据库连接是否打开了事务。
先看在新建事务时的过程:

  1. 358行,传播级别为PROPAGATION_MANDATORY时,抛出异常,这就是这个传播级别必须要在事务方法中调用的原因。
  2. 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这个方法里面的东西。

completeTransactionAfterThrowing.png

很简单,在事务有效的时候,符合需要回滚的异常时,走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上,不会影响外层事务。因为与外层事务融合了,所以外层事务会影响到子事务。

你可能感兴趣的:(spring事务处理的设计与实现--传播级别的具体流程)