• Spring 事务传播机制源码浅析——PROPAGATION_PROPAGATION_NESTED 事务嵌套


    使用示例

    两个方法都使用 PROPAGATION_PROPAGATION_NESTED 传播机制,如下所示:

    在这里插入图片描述
    在这里插入图片描述

    进入核心源码,源码如下:

    在这里插入图片描述

    首先进入 createTransactionIfNecessary() 方法内部的核心流程,可以分成六个核心步骤,源码如下:

    在这里插入图片描述

    第一步骤

    源码如下:

    在这里插入图片描述在这里插入图片描述

    可以发现第一步骤有两行非常重要的代码,第一行代码主要是从 ThreadLocal 类型的变量中去获取值,源码如下:

    在这里插入图片描述

    第一次也就是现在过来的 add() 方法,现在从这个 ThreadLocal 变量中取值,肯定是取不到值的,因为在 add() 方法之前就没有事务过来过,所以这里 add() 方法过来这里 get() 不到值,接着执行第二行代码,源码如下:

    在这里插入图片描述
    在这里插入图片描述

    就是在 txObject 事务对象中保存了两个值 ConnectionHolder 与 boolean newConnectionHolder,此时这两个值ConnectionHolder = null、newConnectionHolder = false,可以理解为第一个步骤就是返回了一个 DataSourceTransactionObject 事务对象,里面保存着两个元素。

    第二步骤

    继续执行第二个步骤,源码如下:

    在这里插入图片描述在这里插入图片描述在这里插入图片描述

    注意第一个步骤中保存的 ConnectionHolder 对象就是 null,所以条件不成立,表示前面根本不存在事务,因为现在还在准备去开启事务的路上呢。

    所以第二步骤第一次过来的 add() 方法判断不成立,不执行 if 里面的逻辑,直接走 else 逻辑。源码如下:

    在这里插入图片描述

    现在 add() 方法就是使用的 PROPAGATION_PROPAGATION_NESTED 事务传播,所以条件成立,直接执行 startTransaction() 开启事务的方法,源码如下:

    在这里插入图片描述

    注意此时会创建一个用于事务内部状态流转的对象,DefaultTransactionStatus 对象保存当前事务是新事务还是老事务,很明显,add() 是第一个 过来开启事务的,所以这个肯定是 new 表示要新建事务,注意现在 add() 方法的堆栈上面保存的 newTransaction 状态为 true,然后继续进入到 doBegin() 方法,源码如下:

    在这里插入图片描述

    可以发现 doBegin() 方法主要干了这几件事情:

    • 把事务设置成 false 手动提交
    • 把 transactionActive 状态设置成 true 表示事务开启了,现在事务是运行状态的
    • 还有这一步非常关键:将 ConnectionHolder(里面封装着 Connection 对象) 存放到了 ThreadLocal 变量中(建立绑定关系)

    至此 add() 方法上面的 @Transactional 注解开启了第一个新事物,然后开始调用目标方法,执行 add() 方法里面具体的逻辑,源码如下:

    在这里插入图片描述

    开始先去执行 add() 方法内部的逻辑,更新 test_user 表数据,当执行到 createOrder() 方法时,发现该方法上也存在 @Transactional 注解的,所以也会去执行上面的六大步骤。

    在这里插入图片描述

    所以此时 createOrder() 方法也去执行代理逻辑,源码如下:

    在这里插入图片描述
    在这里插入图片描述

    此时从 ThreadLocal 变量中是可以获取到 ConnectionHolder(里面封装着 Connection 连接) 对象(因为第一次进来的 add() 把创建的事物保存到了 ThreadLocal 变量中),然后把 ConnectionHolder 保存到了 DataSourceTransactionObject 事务对象中,继续返回上一层,源码如下:

    在这里插入图片描述在这里插入图片描述

    此时这个判断条件完全成立,ConnectionHolder 不为 null,并且 transactionActive 状态是 true,条件都满足,所以就会走进 if 逻辑里面的代码,源码如下:

    在这里插入图片描述

    进入 handleExistingTransaction() 方法,源码如下:

    在这里插入图片描述
    在这里插入图片描述

    首先看到 prepareTransactionStatus() 方法,newTransaction 状态给的是 false,表示不去开始新事务,newSynchroinization 状态给的也是 false,表示不需要在创建一个新的同步状态的对象,记住这两个变量都是 false。并把这两个属性封装到了一个 DefaultTransactionStatus 对象,用于内部事务状态流转使用。

    在这里插入图片描述

    继续看一个非常重要的方法,createAndHoldSavepoint() 方法,从名字可以知道他的作用,肯定是要去创建保存点,然后通过保存点的方式做回滚操作。

    什么是保存点(savepoint)?
     
    举个例子:下面有三条执行语句,先后对 age 字段做了相应的修改,但是有时候我希望保留第一次的修改值,第二、三次修改给我回滚,这个时候就需要使用到保存点 savepoint 了。
     
    update test_user set age = 100 where user_id = 1

    update test_user set age = 200 where user_id = 1
    update test_user set age = 300 where user_id = 1
     

    创建 savepoint 保存点,用来标志回滚到什么位置,
     
    create savepoint a
    update test_user set age = 100 where user_id = 1
    create savepoint b
    update test_user set age = 200 where user_id = 1
    create savepoint c
    update test_user set age = 300 where user_id = 1
     
    回滚操作,只需要 rollback b,这样就只保留了第一次修改,其他两次修改都不会成功,这就是 savepoint 的作用

    进入源码如下:

    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

    获取到 Connection 连接对象,调用 JDBC API 创建保存点。完成之后返回到上一层调用处,要准别开始去执行 createOrder() 目标方法的也逻辑了,源码如下:

    在这里插入图片描述在这里插入图片描述

    执行完 createOrder() 方法之后,就要开始去执行提交操作了,源码如下:

    在这里插入图片描述
    在这里插入图片描述

    进入到 releaseHeldSavepoint() 方法内部:

    在这里插入图片描述在这里插入图片描述

    可以发现这个 releaseHeldSavepoint() 主要是把之前创建好的保存点擦除,并把 AbstractTransactionStatus 事务状态对象中保存点属性置为 null,然后准备去提交操作。为什么要擦除?因为这一步骤中途没有报异常,认为可以正常提交,但是能提交么?往下分析

    提交操作的源码如下:

    在这里插入图片描述

    现在过来的 createOrder() 方法的堆栈上 newTransaction = false,并且 newSynchornization 也是 false,所以提交不了,然后再执行 finally 操作,源码如下:

    在这里插入图片描述

    进入到 cleanupAfterCompletion() 方法,源码如下:

    在这里插入图片描述

    发现里面都执行不了,因为 createOrder() 方法中创建的 DefaultTransactionStatus 对象 newTransaction、newSynchornization 都是 false 状态。并且也没有去挂起老事务,而是使用的同一个事务(同一个 Connection 连接)

    至此两个方法都执行完成,事务正常提交了,但是如果 createOrder() 方法发生了异常是怎么回滚的呢?

    下面开始走进回滚的源码,如下:

    在这里插入图片描述

    进入到 completeTransactionAfterThrowing() 方法,如下:

    在这里插入图片描述

    进入 rollback() 方法内部,源码如下:

    在这里插入图片描述

    现在肯定是有保存点的,createOrder() 过来的时候创建好的保存点,然后执行 rollbackToHeldSavePoint() 方法,方法如下:

    在这里插入图片描述

    进入到 rollbackToSavepoint() 方法,如下所示:

    在这里插入图片描述

    在这里它干了两件事情,获取到 Connection 对象进行保存点的回滚操作(JDBC API 基本接口),然后还干了一件非常重要的事情,就是修改了 rollbackOnly 变量的状态,修改成了 false,这个点非常重要,下面就会分析到为什么要改成 false

    rollbackOnly 变量可以控制事务回滚,就算没有发生异常,可以在满足我们自定义的条件进行事务回滚操作

    如果设置为 false,表示在 commit 操作就一定是去执行 commit 操作,不会再走 rollback 操作了,前面分析过在默认的事务隔离级别下,如果内部方法抛出了异常,外部方法把它 catch 掉,最终整个事务都会回滚,就是因为内部抛出异常的时候,会把这个 rollbackOnly 修改成 true,所以在外部方法 commit 操作的时候,最终执行的是 rollback 回滚逻辑,而不是 commit 操作,所以才会有这样的说法,Spring 中 rollback 一定会 rollback?Spring 中 commit 一定会 commit ?答案肯定不是一定的,好了,回到正题,继续往下分析源码:

    在这里插入图片描述

    然后再回到下面这幅图:

    在这里插入图片描述

    执行 releaseSavepoint() 方法,获取 Connection 擦除保存点。因为事务已经执行完了回滚操作了,保存点也没有存在的意义了。然后再执行 setSavepoint(null),将事务对象中的属性 savepoint 置 null

    在这里插入图片描述

    然后再 rollback 完成之后,还会执行 finally 中的逻辑,源码如下:

    在这里插入图片描述在这里插入图片描述

    执行完 finally 之后,回到上层调用处,源码如下:

    在这里插入图片描述

    开始执行 throw ex ,把异常向上抛出去,此时就会存在两种情况:

    第一种:如果外层捕获了异常,那么外层逻辑正常提交,内部方法的逻辑全部回滚,就产生脏数据了

    第二种:如果外层没有捕获异常,那么异常就会继续向上抛出,最终有抛给了 completeTransactionAfterThrowing() 方法,源码如下:

    在这里插入图片描述

    此时就会进行外层逻辑的回滚操作,至此两个方法的逻辑都被回滚了,就不会产生脏数据。

    至此整PROPAGATION_PROPAGATION_NESTED传播机制就分析完了,总结下嵌套传播,如果没有事务新建事务,如果有,则嵌套在该事务执行,使用的还是同一个 Connection 连接对象,嵌套传播主要是利用保存点进行回滚操作,保存在在正式提交前回擦除,在保存点回滚后也会擦除。

  • 相关阅读:
    基础复习——Button——按钮——触发事件——监听器(单独&公共)——点击事件与长按事件——禁用&恢复按钮...
    基于C#的学生综合教务管理系统
    [WSL] 安装hive3.1.2成功后, 使用datagrip连接失败
    java毕业设计交通事故档案管理系统(附源码、数据库)
    重庆2022农民丰收节 国稻种芯:主会场丰都包鸾镇拉开帷幕
    已解决com.netflix.client.ClientException Eureka客户端异常的正确解决方法,亲测有效!!!
    Git 多账号 SSH 配置
    与“客户”沟通技巧
    利用python加速视屏
    mapstruct常见错误及解决方案
  • 原文地址:https://blog.csdn.net/qq_35971258/article/details/126508009