目录
一、事务在Spring中是如何运作的
1.1 开启事务(DataSourceTransactionManager.doBegin)
二、Spring的事务传播机制
2.1 子事务的传播机制为REQUIRED
2.2 子事务的传播机制为REQUIRES_NEW
2.3 子事务的传播机制为NESTED
当我们在使用Spring所提供的事务功能时,如果是仅仅处理单个的事务,是比较容易把握事务的提交与回滚,不过一旦引入嵌套事务后,多个事务的回滚和提交就会变得复杂起来,各个事务之间是如何相互影响的,是一个值得讨论的点。
一、事务在Spring中是如何运作的
在了解嵌套事务之前,可以先看下单个事务在Spring中的处理流程,以便后面可以更清晰地认识嵌套事务的逻辑。
Spring事务使用AOP的机制实现,会在@Transactional注解修饰的方法前后分别织入开启事务的逻辑,以及提交或回滚的逻辑。@Transactional可以修饰在方法或者类上,区别就在于修饰于类上的,会对该类下符合条件的方法(例如private修饰的方法就不符合条件)前后都织入事务的逻辑。
具体的处理逻辑如下(具体的方法路径为TransactionInterceptor.invoke -> TransactionAspectSupport.invokeWithinTransaction):
1.1 开启事务(DataSourceTransactionManager.doBegin)
这里主要做了获取连接,并关闭自动提交,将@Transactional注解中的一些参数初始化到txObject对象中。
1.2 异常回滚(TransactionAspectSupport.completeTransactionAfterThrowing)
这里是事务异常回滚的地方,这里有个注意点是回滚会先用rollbackOn这个方法判断,默认情况下只有RunTimeException以及Error是会进行回滚的,除非在@Transactional显式声明了rollbackFor。
二、Spring的事务传播机制
当出现多个事务嵌套的场景发生时,Spring事务的处理会变得复杂一些,需要考虑嵌套事务下的提交顺序,以及回滚顺序。对此,Spring提供了多种的传播机制,每种传播机制的效果都不尽相同,以便应对各种复杂的业务场景。
正常处理的嵌套事务流程如下:
传播机制以及它们的效果如下:
REQUIRED:默认值,支持当前事务,如果没有事务会创建一个新的事务
SUPPORTS:支持当前事务,如果没有事务的话以非事务方式执行
MANDATORY:支持当前事务,如果没有事务抛出异常
REQUIRES_NEW:创建一个新的事务并挂起当前事务
NOT_SUPPORTED:以非事务方式执行,如果当前存在事务则将当前事务挂起
NEVER:以非事务方式进行,如果存在事务则抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作
以下的说明均建立在父事务的传播机制为REQUIRED的前提下,来探讨各种传播机制的回滚策略。
2.1 子事务的传播机制为REQUIRED
子事务 | 主事务 | 结果 |
---|---|---|
子事务 | 主事务 | 结果 |
异常 | 正常,并try-catch异常 | 均回滚 |
正常 | 异常 | 均回滚 |
正常 | 异常,并try-catch异常 | 不回滚 |
这里详细说下第一种场景,子事务异常情况下,主事务捕获了子事务的异常却仍发生了回滚。从代码来看:
回滚的原因在于,子事务失败的时候在回滚代码中设置了全局回滚的标识(AbstractPlatformTransactionManager.processRollback)。
之后主事务在进行事务提交时,会判断全局回滚标识是否存在。若存在就会进行回滚动作。(AbstractPlatformTransactionManager.commit)
2.2 子事务的传播机制为REQUIRES_NEW
子事务 | 主事务 | 结果 |
---|---|---|
子事务 | 主事务 | 结果 |
异常 | 正常,并try-catch异常 | 子回滚,主不回滚 |
正常 | 异常 | 子不回滚,主回滚 |
异常 | 正常 | 均回滚 |
这里主要说下第三种场景,因为从REQUIRES_NEW的描述中容易造成误解:创建一个新的事务并挂起当前事务,很容易理解为子事务独立于主事务,子事务回滚后不会影响主事务的执行。
我认为问题点主要在于对挂起的认识,可以看下Spring时如何执行的(AbstractPlatformTransactionManager.handleExistingTransaction):
在这个挂起动作中主要做了两件事,一是将全局ThreadLocal中的配置初始化,二是将原事务的信息保存在SuspendedResourcesHolder对象中。
当子事务异常回滚后,就会通过AbstractPlatformTransactionManager.resume方法恢复主事务。
恢复后,子会再向外抛出一个异常,因此主事务会接收到该异常,因此主事务也发生回滚。
总的来说,挂起的动作并不代表子完全独立于主。
2.3 子事务的传播机制为NESTED
子事务 | 主事务 | 结果 |
---|---|---|
子事务 | 主事务 | 结果 |
异常 | 正常,并try-catch异常 | 子回滚,主不回滚 |
正常 | 异常 | 均回滚 |
异常 | 正常 | 均回滚 |
NESTED是通过rollback的savepoint机制,实现异常回滚。相比于子事务为REQUIRED下,子事务异常,主事务try-catch了异常,REQUIRED情况下主子均会回滚,但NESTED模式主不会回滚。因此可以看下场景一,NESTED是如何处理的。
子事务如果是NESTED模式,则会保存savepoint,以便后面回滚到该savepoint。
回滚时,直接回滚到savepoint,且不会设置全局回滚标识。因此即相当于就是回滚了子事务。主事务由于try-catch了异常,因此执行方法的时候也没有抛出异常,正常走提交流程,且没有全局回滚标识,故不会回滚,正常提交。
PS: mysql的savepoint机制
BEGIN;
INSERT INTO test_entity values (1, 'aaa', 10); ①
SAVEPOINT savepoint1;
INSERT INTO test_entity values (2, 'bbb', 11); ②
ROLLBACK TO savepoint1;
RELEASE SAVEPOINT savepoint1;
COMMIT;
回滚到savepoint1,①会入库,②被回滚。