Spring 事务传播机制源码浅析——PROPAGATION_REQUIRES_NEW

示例代码:

两个方法都使用 PROPAGATION_REQUIRES_NEW 传播机制进行演示,并分析源码:

示例如下:

在这里插入图片描述


在这里插入图片描述

还是要进入 TransactionInterceptor 类,这各类是入口,这里截取出核心源码如下:

在这里插入图片描述

进入 createTransactionIfNecessary() 方法,可以分成六个核心步骤,源码如下:

在这里插入图片描述

第一步骤

源码如下:

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述


在这里插入图片描述

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

第二步骤

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

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

在这里插入图片描述

现在 add() 方法就是使用的 PROPAGATION_REQUIRES_NEW 事务传播,所以条件成立,直接执行 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() 方法,源码如下:

在这里插入图片描述

此时因为我们在 createOrder() 方法上配置的就是 PROPAGATION_REQUIRES_NEW 传播机制,所以条件成立,进入 if 逻辑。执行 suspend() 挂起逻辑,然后再执行 startTransaction() 方法新建新事务。

那么怎么将事务挂起呢?又是怎么新建事务的呢?

先进入 suspend() 源码如下:

在这里插入图片描述

doSuspend() 方法干了两件事,第一件事将 transaction 事务对象中的属性 ConnectionHolder 清空了(里面有第一次获取到的 Connection 对象) , 第二件事解绑第一次过来的新建的事务,其实就是 add() 方法绑定的事务,猜猜为什么要解绑,因为 createOrder() 方法要自己新建一个事务,新建事务肯定要绑定事务,所以这里得腾出坑位来,源码如下:

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述

但是真就把原来的事务丢弃了不管了么?答案肯定是不可能的,你要是丢弃了,之前的 add() 方法的事务不就失效了么,所以得把 remove() 之后对象保存起来,那么存在哪里么?源码如下:

在这里插入图片描述

在这里插入图片描述

直接封装到了 SuspendedResourcesHolder 对象中,还有一些 readOnly、wasActive等属性都在,其实就相当于把前一个事务的属性都给 copy 到了 SuspendedResourcesHolder 新对象了。至此 suspend() 方法就执行完了,那么总结下 suspend() 方法就干了两件事:

  • 解绑已经存在的事务(其实就是清空 ThreadLocal 中的 ConnectionHolder 对象(里面封装着 Connection 对象))
  • 将原来的事务包装到了一个 SuspendedResourcesHolder 对象中,暂存起来了

接着继续往下执行 startTransaction() 方法,源码如下(注意这里把原来的老事务传进去了):

在这里插入图片描述

在这里插入图片描述

其实 startTransaction() 方法就做三件事情:

  • 新建事务状态对象,并且 newTransaction 状态默认为 true,并且把 suspendResources 老事务保存起来了
  • 将 ConnectionHolder 状态也设置为 true
  • 开启事务
    • 修改 commit 装态为 false,表示使用手动提交事务
    • 绑定新建的事务,其实就是将 Connection 保存到了 ThreadLocal 中
    • 设置 transactionActive 状态为 true,表示当前事务激活了正在运行中

执行完 startTransaction() 方法直接返回,返回后表示事务已经开启好了,那么现在就要去执行目标方法的业务逻辑了,源码如下:

在这里插入图片描述

在这里插入图片描述

执行完 createOrder() 那么开始准备去提交了,源码如下:

在这里插入图片描述


在这里插入图片描述

因为 createOrder() 方法开启的事务也是一个新事物,所以 newTransaction 状态也是 true,所以这里可以去执行提交,此时 createOrder() 方法事务已经提交完成了,但是提交完之后还要做一些事情,源码如下:

在这里插入图片描述

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

在这里插入图片描述

先看 doCleanupAfterCompletion() 方法干了什么吧,源码如下:

在这里插入图片描述

可以总结为3件事情:

  • 解绑事务(清空 ThreadLocal 中的 Connection 对象)
  • 恢复事务为自动提交
  • 将事务对象的属性 ConnectionHolder 设置为 null,因为这都已经提交了,所以不需要使用 Connection 对象了

然后再看提交后要做的第二件事,因为 suspendResources 里面保存的是前一个事务对象,所以不为 null:

在这里插入图片描述

进入执行 resume() 方法,看名字意思是恢复xxx,那么肯定是要把之前的挂起的事务重新拿回来呗,源码如下:

在这里插入图片描述

在这里插入图片描述

总结起来 resume() 方法就是把之前挂起的事务属性重新搞回来,绑定回 ThreadLocal 中,并且还有一堆同步状态的属性全部原封不动的放回到事物装填管理器中。

至此 createOrder() 方法整个结束,代码继续往下执行,回到 add() 方法业务逻辑,然后执行完 add() 方法的业务逻辑,就要开始执行 add() 方法的提交了,resume() 方法已经将 add() 方法开启的事务相关的数据都一一复原了,所以 add() 方法的提交操作和 createOrder() 方法一模一样,只是两个方法使用的不是同一个事务管理的而已。

如果 createOrder() 方法抛出异常,会怎么处理呢?分析源码:

在这里插入图片描述

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

在这里插入图片描述


在这里插入图片描述

因为此时 createOrder() 是自己开启的新事务,所以 newTransaction = true 成立,开始执行真正的回滚操作。

然后执行 finally 逻辑,finally 逻辑上面已经分析过了,这里就不在过多描述了,源码如下:

在这里插入图片描述


在这里插入图片描述

可以发现内部方法的提交和回滚都是自己控制的,和外部事务没有任何关系。两个事务相当于是隔离开的。

最后总结一下 PROPAGATION_REQUIRES_NEW 事务,不管是否存在事务,都会自己新建事务,自己控制事务,互不叨扰,而且后面的事务会把之前的事务 suspend 挂起(其实就是把之前获取到的 Connection 对象暂存到其他地方而已了,当后面的事务提交或者回滚之后又会把之前的事务全部还原回来而已)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340