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

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

作者: 夜秦淮 | 来源:发表于2019-12-29 21:26 被阅读0次

    问题

    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事务处理的设计与实现--传播级别的具体流程

          本文链接:https://www.haomeiwen.com/subject/kxnsoctx.html