通过分析一些事务嵌套的场景,来深入理解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)。
本文中某些是抄写网上的一些好的整理,在此汇总,也加深下自己的印象。