无需看源码了解并解决一个事务常见的异常

448 阅读3分钟

无需看源码了解并解决一个事务常见的异常

在观看此篇文章之前需要了解什么是事务的传播属性

在观看此篇文章之前需要了解什么是事务的传播属性

在观看此篇文章之前需要了解什么是事务的传播属性

Transaction rolled back because it has been marked as rollback-only

相信大家在使用Spring事务的时候有概率会碰到一个异常,这个异常就是UnexpectedRollbackException异常的描述就是上面所写的。至于这个异常是怎么报出来的呢?我们先模拟出来,然后进行着手分析。我们模拟的场景是转钱的场景,即A给B转100块钱。

 1@Service
2public class UserAccountA {
3
4    @Autowired
5    private JdbcTemplate jdbcTemplate;
6
7    @Autowired
8    private UserAccountB userAccountB;
9
10    @Transactional
11    public void eventTwo() throws RollbackException {
12        jdbcTemplate.execute("UPDATE USER SET MONEY = MONEY - 100 WHERE NAME = 'A'");
13        userAccountB.eventTwo();
14    }
15
16}
 1@Service
2public class UserAccountB {
3
4    @Autowired
5    private JdbcTemplate jdbcTemplate;
6
7    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = RollbackException.class)
8    public void eventTwo() throws RollbackException {
9        jdbcTemplate.execute("UPDATE USER SET MONEY = MONEY + 100 WHERE NAME = 'B'");
10        throw new RollbackException();
11    }
12
13}
1public class RollbackException extends Exception {
2}

上面的三个类分别是用户A、用户B、和自定义的异常。这时候我们运行程序的时候就能看到那个异常了。那么为什么会报此异常呢。这里我们有两个需要注意的点。

  • 用户B定义的传播属性:Propagation.REQUIRED此传播属性下的意思就是支持当前事务,如果当前没有事务那么就新建事务,如果有事务就加入此事务。所以此时B方法加入到了A方法创建的事务中
  • 自定义异常类:我们自定义的异常类是继承了Exception。下面的是官方文档的一句话,意思就是默认情况下当抛出RuntimeException才会回滚。

By default, a transaction will be rolling back RuntimeException and Error

有了上面两个知识的补充,下面我们开始讲为什么会报这个异常,我们都知道Spring的事务其实是用了动态搭理的原理实现的。

大概嵌套逻辑就像上图一样,但是在B方法执行完成以后抛出了异常,需要回滚,但是并不会真正的回滚,而是将此事务标记为rollback-only,当A方法执行完以后,我们可以看到在A方法上面并没有rollbackFor这个参数设置,而抛出的异常也是继承了Exception所以A方法执行完以后想要提交事务,所以此时就会产生冲突。

同一个事务中,一个方法想要回滚,一个方法想要提交。当然会抛异常了。

解决办法

解决办法很简单,首先我们看是什么导致了这个问题。同一个事务中,一个方法想要回滚,一个方法想要提交。。导致这个问题原因有两个。

  1. 同一个事务
  2. 一个想要回滚,一个想要提交

所以针对上面原因我们可以有两种不同的解决方法。

  1. 将B方法的传播属性改为PROPAGATION_REQUIRES_NEW,即不会和A共用一个事务,会自己新启动一个事务。此时A和B两个方法互不干扰。
  2. 在A方法上的事务注解上加上rollbackFor = RollbackException.class参数,此时A方法收到B方法抛出的异常后,也会回滚了。

本文代码地址