Transaction-propagation
年年岁岁花相似,岁岁年年人不同。
SpringBoot 事务传播行为
准备
pom 依赖,这里 ORM 框架使用 Mybatis-Plus, AOP 用于获取代理对象,因为 Spring 事务基于 AOP 实现,某些场景需要使用 当前类的代理对象,不然会导致事务失效。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
</dependencies>
获取当前项目 PlatformTransactionManager 实现类包名
开启日志打印
logging:
level:
org.springframework.jdbc.support: debug
以转账操作为例
- transfer:转账
- deduct:扣款
- add:到账
不使用代理对象调用本类方法
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void deduct(Integer id, BigDecimal money) {
baseMapper.deduct(id, money);
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void add(Integer id, BigDecimal money) {
baseMapper.add(id, money);
}
@Override
@Transactional(rollbackFor = NoClassDefFoundError.class, propagation = Propagation.REQUIRED)
public void transfer(Integer from, Integer to, BigDecimal money) {
deduct(from, money);
add(to, money);
throw new RuntimeException("error");
}
日志中显示只开启了一个事务
开启 AOP 代理
@EnableAspectJAutoProxy(exposeProxy = true)
@Override
@Transactional(rollbackFor = NoClassDefFoundError.class, propagation = Propagation.REQUIRED)
public void transfer(Integer from, Integer to, BigDecimal money) {
BalanceServiceImpl balanceService = (BalanceServiceImpl) AopContext.currentProxy();
balanceService.deduct(from, money);
balanceService.add(to, money);
throw new RuntimeException("error");
}
日志打印: 开启了3个事务
MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
调用 transfer 方法,add 方法出现异常,deduct 方法与 add 方法处于不同事务,但是还是分别回滚了。
按道理 add 方法抛出异常导致其事务回滚;在 transfer 方法中没有捕获该 RuntimeException,会被继续抛出,但是这个 RuntimeException 并不在 rollbackFor = NoClassDefFoundError.class 的规则的范围内,为什么会回滚呢?
启用代码调试,在 TransactionAspectSupport 的 completeTransactionAfterThrowing 方法决定了出现那些异常才需要进行回滚
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
然后 txInfo.transactionAttribute.rollbackOn(ex) if 判断来到 RuleBasedTransactionAttribute 的 rollbackOn 方法
该方法中由于 RuntimeException 不符合 rollbackFor = NoClassDefFoundError.class 规则,又进一步调用了 DefaultTransactionAttribute 的 rollbackOn 方法,显然该方法会返回 true,因此最终该事务也会回滚。
根据提示,我们可以自定义业务异常,即便发生了某个业务异常(Checked),在某些情况下,我们也不希望事务被回滚。
自定义异常 ,执行 transfer 方法后,add 方法依然回滚,transfer 所在事务被提交
public class BusinessException extends Exception{
public BusinessException(String message) {
super(message);
}
}
REQUIRED
默认事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:
- 如果外部方法没有开启事务的话,
Propagation.REQUIRED
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。 - 如果外部方法开启事务并且被
Propagation.REQUIRED
的话,所有Propagation.REQUIRED
修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。
调用 transfer 方法,add 方法出现异常,deduct 方法与 add 方法处于同一事务,一同回滚。
NESTED
如果当前存在事务,就在嵌套事务内执行;如果当前没有事务,就执行与TransactionDefinition.PROPAGATION_REQUIRED
相同行为。
- 在外部方法开启事务的情况下,在内部开启一个新的事务,作为嵌套事务存在。
- 外部事务回滚,嵌套事务也会回滚
- 内部事务回滚,外部事务根据异常回滚规则决定是否回滚
- 如果外部方法无事务,则单独开启一个事务,与
PROPAGATION_REQUIRED
类似。
SUPPORT
支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
NOT_SUPPORTED
以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
NEVER
以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
总结
Spring 中的事务传播行为有:
- MANDATORY
- REQUIRES_NEW
- REQUIRE
- NESTED
- SUPPORT
- NOT_SUPPORTED
- NEVER
除了 PROPAGATION_NOT_SUPPORTED 和 PROPAGATION_NEVER,其它传播行为均支持事务,其中:
如果外部方法 A() 中调用了 方法 a() ,A() 未捕获 a() 抛出的异常, 并且 a() 发生以下异常会导致 A() 事务回滚
- RuntimeException
- Error
- 符合 A() 事务的 rollbackFor 规则,不在 noRollbackFor 规则
版权声明:如无特别声明,本站收集的文章归 HuaJi66/Others 所有。 如有侵权,请联系删除。
联系邮箱: GenshinTimeStamp@outlook.com
本文标题:《 Transaction-propagation 》