使用示例
两个方法都使用 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 = 1update 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 连接对象,嵌套传播主要是利用保存点进行回滚操作,保存在在正式提交前回擦除,在保存点回滚后也会擦除。