事务传播

写在前面

Spring的事务是通过AOP这种代理的方式实现的。

事务传播就是多个事务方法相互调用时,事务如何在这些方法间传播。比如事务方法A调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务执行造成影响,而方法A的事务对方法B的事务执行也有影响,这种影响就由两个方法定义的事务传播类型所决定。

spring中的事务传播定义了七种类型:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。这七种类型以枚举的形式存储在org.springframework.transaction.annotationPropagation

下面根据集体的例子来解释每一个情况。

假设现在有两个方法A和B,A会在ATable中插入一条数据,B会在BTable中插入一条数据。伪代码如下:

1
2
3
4
5
6
7
8
// 将传入参数a存入ATable
pubilc void A(a){
insertIntoATable(a);
}
// 将传入参数b存入BTable
public void B(b){
insertIntoBTable(b);
}

假设没有事务时,如下场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void testMain(){
// 调用A入参a1
A(a1);
// 调用testB
testB();
}

public void testB(){
// 调用B入参b1
B(b1);
// 发生异常抛出
throw Exception;
// 调用B入参b2
B(b2);
}

这种情况下,a1和b1这两条数据会存入对用的数据库,而b2这条数据会因为异常而没有入库。

REQUIRED(Spring默认的事务传播)

如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务

使用场景如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
// 调用A入参a1
A(a1);
// 调用testB
testB();
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
// 调用B入参b1
B(b1);
// 发生异常抛出
throw Exception;
// 调用B入参b2
B(b2);
}

这种情况下,数据库的状态会停留在没有执行这两个方法的状态。

testMain()声明了事务,它执行时会创建事务,在执行testB()的时候,它就会加入testMain()的事务,而不会自己创建。

SUPPORTS

当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

1
2
3
4
5
6
7
8
9
10
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.SUPPORTS)
public void testB(){
B(b1); //调用B入参b1
throw Exception; //发生异常抛出
B(b2); //调用B入参b2
}

这种情况下,a1和b1会插入数据库,b2不会插入,因为testMain()是没有事务的,所以执行testB()方法时,无法加入其他的事务。

MANDATORY

当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。

上面的代码在这种情况下,执行结果就是a1存储数据库,而b1和b2没有存入,因为testMain()没有事务而产生报错。

REQUIRES_NEW

创建一个新事务,如果存在当前事务,则挂起该事务。可以理解为在执行时,不论当前是否存在事务,总是会新建一个事务。

1
2
3
4
5
6
7
8
9
10
11
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
throw Exception; //发生异常抛出
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB(){
B(b1); //调用B入参b1
B(b2); //调用B入参b2
}

这种情况下,a1没有存入成功,而b1和b2会存入成功。因为testB()会新建一个事务,而不是采用testMain()的事务。

NOT_SUPPORTED

始终以非事务方式执行,如果当前存在事务,则挂起当前事务,可以理解为始终不执行事务。

NEVER

不使用事务,如果当前事务存在,则抛出异常

NESTED