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 连接对象,嵌套传播主要是利用保存点进行回滚操作,保存在在正式提交前回擦除,在保存点回滚后也会擦除。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 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