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 对象暂存到其他地方而已了,当后面的事务提交或者回滚之后又会把之前的事务全部还原回来而已)

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...