通过分析一些事务嵌套的场景,来深入理解spring的事务传播机制。

1:简单举例分析

假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()

PROPAGATION_REQUIRED(spring 默认) required  (需要)

如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。

假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。

这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。

PROPAGATION_REQUIRES_NEW (require_new 新的)

比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED(需要),ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW(新的)。

那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,ServiceA.methodA()才继续执行。

他与 PROPAGATION_REQUIRED (需要)的事务区别在于事务的回滚程度了。

因为 ServiceB.methodB() 是新起一个事务,那么就是存在两个不同的事务。

如果 ServiceB.methodB() 已经提交,那么 ServiceA.methodA() 失败回滚,ServiceB.methodB() 是不会回滚的。

如果 ServiceB.methodB() 失败回滚,如果他抛出的异常被 ServiceA.methodA() 捕获,ServiceA.methodA() 事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。

PROPAGATION_SUPPORTS(support 支持)

假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。

PROPAGATION_NESTED(nested 嵌套)

现在的情况就变得比较复杂了, ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? 
ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:

a、捕获异常,执行异常分支逻辑

void methodA() { 
 
        try { 
 
            ServiceB.methodB(); 
 
        } catch (SomeException) { 
 
            // 执行其他业务, 如 ServiceC.methodC(); 
 
        } 

    }

这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED(需要) 和 PROPAGATION_REQUIRES_NEW (新的)都没有办法做到这一点。

b、 外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback。

另外三种事务传播属性基本用不到,在此不做分析。

 

2:详细分析嵌套事物

什么是嵌套事务?

嵌套事务是一个外部事务的一个子事务,是一个外部事务的一个组成部分,当嵌套事务发生异常,而回滚,则会回复到嵌套事务的执行前的状态,相当于嵌套事务未执行。

如果外部事务回滚,则嵌套事务也会回滚!!!外部事务提交的时候,它才会被提交。

先说明下嵌套事物的重点:

内层事务依赖于外层事务。

外层事务失败时,会回滚内层事务所做的动作。

而内层事务操作失败并不会引起外层事务的回滚。

Savepoint点。

就是说外部事物提交,嵌套的内部事务才会提交,

外部事物回滚了,内部事务也会跟着回滚。

 

其中嵌套事务比较难理解的,难区分的两个传播属性:require_new 和 nested

1:PROPAGATION_REQUIRES_NEW

因为它总是开启一个新的事务,当然和原有的事务不相干了。

2:另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务,  它是已经存在事务的一个真正的子事务。潜套事务开始执行时,  它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint。潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。

3:由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于:

PROPAGATION_REQUIRES_NEW 完全是一个新的事务,

而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back. 

3:那到底什么是真正的事务嵌套呢?代码举例说明

外部事务如何利用嵌套事务的 savepoint 特性呢, 我们用代码来说话 

1. ServiceA {  
2.     /** 
3.      * 事务属性配置为 PROPAGATION_REQUIRED 
4.      */  
5.     void methodA() {  
6.         ServiceB.methodB();  
7.     }  
8. }  
9. ServiceB {  
10.     /** 
11.      * 事务属性配置为 PROPAGATION_REQUIRES_NEW 
12.      */   
13.     void methodB() {  
14.     }  
15. }

这种情况下,因为ServiceB#methodB 方法的事务属性为require_new(总是开启一个新的事务,并挂起原有事务),

所以两者不会发生任何关系,ServiceA#methodA 和 ServiceB#methodB 不会因为对方的执行情况而影响事务的结果。

因为它们本身就是两个事务,在ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了。

那么nested 又怎么理解呢?

16. ServiceA {  
17.     /** 
18.      * 事务属性配置为 PROPAGATION_REQUIRED 
19.      */  
20.     void methodA() {  
21.         ServiceB.methodB();  
22.     } 
23. }  
24. ServiceB {  
25.     /** 
26.      * 事务属性配置为 PROPAGATION_NESTED 
27.      */   
28.     void methodB() {  
29.     }   
30. }

现在的情况就变得比较复杂了, ServiceB#methodB 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢?

ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(潜套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:

1:改写 ServiceA 如下:

1. ServiceA {  
2.     /** 
3.      * 事务属性配置为 PROPAGATION_REQUIRED 
4.      */  
5.     void methodA() {  
6.         try {  
7.             ServiceB.methodB();  
8.         } catch (SomeException) {  
9.             // 执行其他业务, 如 ServiceC.methodC();  
10.         }  
11.     }  
12. }

这种方式也是潜套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。

2. 代码不做任何修改, 那么如果内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 

   外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback (+MyCheckedException)。

 

本文中某些是抄写网上的一些好的整理,在此汇总,也加深下自己的印象。