Spring中提供了事务的增强功能,即事务的传播,不属于数据库,是Spring框架提供的,不同的事务传播行为,带来不同的事务特性,Spring提供了其中事务传播行为,在Propagation枚举中进行了定义。

事务传播行为

七种事务传播行为:

事务传播行为类型 说明
Propagation.REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。默认的。
Propagation.SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
Propagation.MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
Propagation.REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
Propagation.NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
Propagation.NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
Propagation.NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

验证例子

REQUIRED

如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 业务方法一 serviceOne
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void addRequiredException(){
UserInfo userInfo = new UserInfo();
userInfo.setUserAccount("wangwu");
userInfoDAO.insertOne(userInfo);
long np = userInfo.getValid().longValue();
}
// 业务方法二 serviceTwo
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void addRequired() {
UserInfo userInfo = new UserInfo();
userInfo.setUserAccount("wangwu");
userInfoDAO.insertOne(userInfo);
}

// 调用两个业务
public void insertWithException() {
serviceTwo.addRequired();
serviceOne.addRequiredException();
}

分析:

  • 如果调用方法上没有添加事务,则1,2两个插入分别会创建一个自己的事务,进行各自的数据库操作,即1插入失败回滚,2插入成功。
  • 我们想要的结果是1和2要么全部成功,要么全部失败,这时只需要在调用的方法上添加事务即可@Transactional(rollbackFor = Exception.class),让两个方法都加到此事务中处理。

REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起,在自己的事务中执行。

调用者不加事务

测试:

1
2
3
4
5
6
7
8
9
10
11
12
/** 测试1:外围方法没开启事务,并抛出异常 均会插入数据*/
public void insertNoTransactionalRequiredNew() {
serviceOne.addRequiredNew();
serviceTwo.addRequiredNew();
throw new RuntimeException();
}
/** 测试1:外围方法没开启事务,调用的业务方法中抛出异常*/
public void insertNoTransactionalRequiredNewTwo() {
serviceOne.addRequiredNew();
// 自己的事务回退了不会插入
serviceTwo.addRequiredNewException();
}

分析:

  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
@Transactional(rollbackFor = Exception.class)
public void insertTransactionalRequiredNew() {
// 加入到调用者事务中 不会插入
serviceOne.addRequired();
// 自己新的事务 插入成功
serviceOne.addRequiredNew();
// 插入成功
serviceTwo.addRequiredNew();
throw new RuntimeException();
}
@Transactional(rollbackFor = Exception.class)
public void insertTransactionalRequiredNewTwo() {
// 加入到调用者事务中
serviceOne.addRequired();
serviceOne.addRequiredNew();
// 自己的事务回退了不会插入,将异常往上抛,导致调用者事务也回退了
serviceTwo.addRequiredNewException();
}
@Transactional(rollbackFor = Exception.class)
public void insertTransactionalRequiredNewThree() {
// 加入到调用者事务中
serviceOne.addRequired();
serviceOne.addRequiredNew();
try {
serviceTwo.addRequiredNewException();
} catch (Exception e) {
log.error("捕获异常:{}",e.toString());
}
}

分析:

  1. 调用者有事务,并且抛出异常了,只会回退本方法中的事务,两个子方法是独立的事务运行,因此不会回退;
  2. 如果调用的业务方法中抛出异常了,调用者也会捕获,因此它的事务也会回退。

NESTED

如果当前存在事务,则在嵌套事务内执行。

分析:

  1. 外部调用者如果不加事务,那么表现与REQUIRED方式一样;
  2. 外部调用者添加了事务,被调用的方法则是其子事务;
  3. 调用者异常回退,子事务也会回退;
  4. 子事务中如果有异常,向上抛,则事务回退,自己消化掉,则只会回退子事务。

对比

NESTED和REQUIRED修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是REQUIRED是加入外围方法事务,所以和外围事务同属于一个事务,一旦REQUIRED事务抛出异常被回滚,外围方法事务也将被回滚。而NESTED是外围方法的子事务,有单独的保存点,所以NESTED方法抛出异常被回滚,不会影响到外围方法的事务。
NESTED和REQUIRES_NEW都可以做到内部方法事务回滚而不影响外围方法事务。但是因为NESTED是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而REQUIRES_NEW是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。