概念
Spring 在 TransactionDefinition 接口中规定了 7 种类型的事务传播行为。事务传播行为是 Spring 框架独有的事务增强特性,他不属于的事务实际提供方数据库行为。
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。
**ps.**所谓事务的嵌套就是两个事务方法之间相互调用。spring事务开启 ,或者是基于接口的或者是基于类的代理被创建(注意一定要是代理,不能手动new 一个对象,并且此类(有无接口都行)一定要被代理——spring中的bean只要纳入了IOC管理都是被代理的)。所以在同一个类中一个方法调用另一个方法有事务的方法,事务是不会起作用的。
类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 默认事务类型,如果没有,就新建一个事务;如果有,就加入当前事务。适合绝大多数情况。 |
PROPAGATION_REQUIRES_NEW | 如果没有,就新建一个事务;如果有,就将当前事务挂起。 |
PROPAGATION_NESTED | 如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。 |
PROPAGATION_SUPPORTS | 如果没有,就以非事务方式执行;如果有,就使用当前事务。 |
PROPAGATION_NOT_SUPPORTED | 如果没有,就以非事务方式执行;如果有,就将当前事务挂起。即无论如何不支持事务。 |
PROPAGATION_NEVER | 如果没有,就以非事务方式执行;如果有,就抛出异常。 |
PROPAGATION_MANDATORY | 如果没有,就抛出异常;如果有,就使用当前事务。 |
PROPAGATION_REQUIRED
-
如果ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部,就不再起新的事务。这时只有外部事务并且他们是共用的,所以这时ServiceA.methodA或者ServiceB.methodB无论哪个发生异常methodA和methodB作为一个整体都将一起回滚
-
如果ServiceA.methodA没有事务,ServiceB.methodB就会为自己分配一个事务。这样,在ServiceA.methodA中是没有事务控制的。只是在ServiceB.methodB内的任何地方出现异常,ServiceB.methodB将会被回滚,不会引起ServiceA.methodA的回滚
PROPAGATION_REQUIRES_NEW
设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。
-
如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。
-
如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA的try…catch捕获并处理,ServiceA.methodA事务仍然可能提交;如果他抛出的异常未被ServiceA.methodA捕获处理,ServiceA.methodA事务将回滚。
PROPAGATION_NESTED
开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.
比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_NESTED,那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的子事务并设置savepoint,等待ServiceB.methodB的事务完成以后,他才继续执行。因为ServiceB.methodB是外部事务的子事务,那么
-
如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB也将回滚。
-
如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA的try…catch捕获并处理,ServiceA.methodA事务仍然可能提交;如果他抛出的异常未被ServiceA.methodA捕获处理,ServiceA.methodA事务将回滚。
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是:
PROPAGATION_REQUIRES_NEW 完全是一个新的事务,它与外部事务相互独立; 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.
PROPAGATION_SUPPORTS
如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行
PROPAGATION_NOT_SUPPORTED
当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。
PROPAGATION_NEVER
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,那么ServiceB.methodB就要抛出异常了
PROPAGATION_MANDATORY
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常
场景演示
场景一
2个PROPAGATION_REQUIRED事务
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public int insertSelective(AccountMsg record) {
return accountMsgMapper.insertSelective(record);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public int updateByPrimaryKeySelective(AccountMoney record) {
int a = 1/0;
return accountMoneyMapper.updateByPrimaryKeySelective(record);
}
msgService.insertSelective(msg);
moneyService.updateByPrimaryKeySelective(accountMoney);
两个方法都在一个事务中,要成功都成功,要失败都失败!
场景二
PROPAGATION_REQUIRED和PROPAGATION_REQUIRES_NEW组合
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public int insertSelective(AccountMsg record) {
int a = 1/0;
return accountMsgMapper.insertSelective(record);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public int updateByPrimaryKeySelective(AccountMoney record) {
return accountMoneyMapper.updateByPrimaryKeySelective(record);
}
moneyService.updateByPrimaryKeySelective(accountMoney);
msgService.insertSelective(msg);
看方法的执行顺序,先执行了updateAccountRequiresNew ,再执行insertAccountMessageRequired,
异常是在insertAccountMessageRequired抛出的,因为updateAccountRequiresNew这个方法新建了一个事务,他的方法执行完就commit。insertAccountMessageRequired这个rollback了。
场景三
PROPAGATION_NESTED嵌套事务
-
联合成功(PROPAGATION_REQUIRED的特性)
-
隔离失败(PROPAGATION_REQUIRES_NEW的特性)。
首先全部使用PROPAGATION_REQUIRED进行测试:
@Transactional(rollbackFor = Exception.class)
public void updateAccountTest2(AccountMoney accountMoney) {
try {
AccountMsg msg = new AccountMsg();
msg.setBizPk(accountMoney.getBizPk());
msg.setChangeDate(new Date());
msg.setChangeRemark("修改用户:"+accountMoney.getAcctName()+",金额:"+accountMoney.getChangeAmt()+"元");
try {
msgService.insertSelective(msg);
} catch (Exception e) {
log.error("insert service exception, msg : {}", e.getMessage());
moneyService.updateByPrimaryKeySelective(accountMoney);
// 如果注释 Transaction rolled back because it has been marked as rollback-only。
// throw e;
}
} catch (Exception e) {
log.error("Transactional exception, msg :{}", e.getMessage());
throw e;
}
}
结果与场景一相同,两张表都没有更新,事务保持一致。
PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务。 嵌套事务开始执行时, 它将取得一个 savepoint。如果这个嵌套事务失败, 我们将回滚到此 savepoint。 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public int insertSelective(AccountMsg record) {
int a = 1/0;
return accountMsgMapper.insertSelective(record);
}
// 父级事务
try {
msgService.insertSelective(msg);
} catch (Exception e) {
// 此处不应该throw exp,否则父级事务也会回滚
log.error("insert service exception, msg : {}", e.getMessage());
moneyService.updateByPrimaryKeySelective(accountMoney);
}
结果,updateByPrimaryKeySelective更新了,但是insertSelective没有插入新数据。
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于,PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit,这个规则同样适用于 roll back。
NESTED的具体用途如下:在此方法出现异常时,通过TRY CATCH 代码块包含住, 继续往下执行或者执行CATCH中的处理。此点REUQIRED做不到, REQUIRED_NEW能做到, 但它是单独的事务,不与父类一块提交的。