前言

我们今天来学习关于事务的传播行为的知识。

题目一

题目如下:

某个复杂的计算逻辑,由若干个子流程处理 + 主流程处理共同完成。

业务要求是 :

1)子流程处理互相独立,即其中一个发生错误事务回滚,不影响其他子流程的处理;

2)主流程处理失败,事务回滚时,所有的子流程也要跟着回滚。

思考

鄙人苦苦思索良久,绞尽脑汁,穷尽我以前所学知识,憋出了如下的答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class A {
@Transactional
public void methodA()
{
// todo
}
}
class B {
@Transactional
public void methodB()
{
// todo
}
}

@Transactional
public void methodMain(){
A a=new A();
B b=new B();
try {
//子流程A
try {
a.methodA();
}catch (Exception e) {
e.printStackTrace();
}
//子流程B
try {
b.methodB();
}catch (Exception e){
e.printStackTrace();
}
//todo ...主流程
}catch (Exception e) {
e.printStackTrace();
//手动回滚
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
}

我这样设计明显是有问题的。@Transactional使用的默认传播行为是Propagation.REQUIRED。它的解释是如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。methodA()、methodB()及主流程都处于同一个事务当中,那么试想,当有异常发生时,methodA()和methodB()如何独自进行回滚操作而不影响主流程呢?很明显没有额外的操作来保证这一点。

正解

正确的设计如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Transactional
public void methodMain(){

//子流程A
try {
methodA();
}catch (Exception e) {
e.printStackTrace();
}
//子流程B
try {
methodB();
}catch (Exception e){
e.printStackTrace();
}
//todo ...主流程

}

@Transactional(propagation = Propagation.NESTED)
public void methodA()
{
// todo
}

@Transactional(propagation = Propagation.NESTED)
public void methodB()
{
// todo
}

这里的设计思想主要为:

将每个子流程的方法都设计为public的,且子流程的方法事务传播行为是NESTED,且在主流程中用 try...catch 捕获每个子流程的异常。

依据的原理:

NESTED:如果主流程事务失败,抛出异常,则主流程及所有子流程都会失败回滚;如果某个子流程抛出异常,若主流程未捕获,则会导致所有流程都会回滚,若主流程捕获异常,则子流程会回滚,且不会导致主流程回滚

事务的传播行为

定义

事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B, A和B⽅法本 身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播⾏ 为。 A调⽤B,我们站在B的⻆度来观察来定义事务的传播⾏为 ,如下图所示,一共有七种传播行为:

注意:Spring默认的事务传播值是Propagation.REQUIRED

题目二

ClassA中有两个方法分别是methodA和methodB,下面的写法有什么问题?

1
2
3
4
5
6
7
8
9
10
public class ClassA{
public void methodA(){
this.methodB();
}

@Transactional
public void methodB(){
//do sth
}
}

通过实战测试,发现此处methodB()中的@Transactional不会生效。

分析

原因如下:

Spring的事务管理是通过AOP实现的,其AOP的实现对于非final类是通过cglib实现,然后在调用方法时,会判断这个方法有没有@Transactional注解,如果有的话,则通过动态代理实现事务管理(拦截方法调用,执行事务等切面)。当methodA()中调用methodB()时,发现methodA()上并没有@Transactional注解,所以整个AOP代理过程(事务管理)不会发生。

解决方法

方法一

将methodB()方法定义在不同的类中,此时@Transactional注解就会生效。

方法二

将@Transactional放在methodA()上。

方法三

在methodA()中获取当前类的代理对象,通过代理对象调用methodB()。

总结

我们主要通过两道题目来认识事务的传播机制,共有七种传播机制,对NESTED嵌套事务有了实战的经验。

参考资料

Spring事务传播行为详解

看完就明白_spring事务的7种传播行为