Spring事务异常rollback-only深入探究

首先rollback-only出现的原因先简单带过一下吧:在使用了Propagation.REQUIRED的事务传递中,若本层的service捕获了下层service的异常,则本层中的事务也无法提交,在方法结束,事务尝试提交时会报出Transaction rolled back because it has been marked as rollback-only错误。这是因为REQUIRED是同一个事务,具有原子性。当内层的方法出现异常的时候,会标记一个rollback-only,这样外层方法提交的时候,判断rollback-only为true,那么整个事务都不允许提交。参考代码如下:(以下代码全部基于service层方法级别注解,且事务传播机制为REQUIRED)

service1
public String getName(String name) {
    try {
        demoService2.insertUser();
  }catch (Exception e) {
    e.printStackTrace();
  }
  demoService3.insertUser();
  return "1";
}
service2
public void insertUser()  {
    userDao.insertUserError();//这里报错,字段超长
    
}
service3
public void insertUser() {
    userDao.insertUser1(); //这里正常插入一条数据
  }

这样执行service1.getName后,service3的insertUser也是无法提交的。原因就是上边说的。

接下来对service2进行一下修改

service2
public void insertUser()  {
    try {
        userDao.insertUserError();//这里报错,字段超长
    }catch (Exception e) {
    e.printStackTrace();
  }
    
}

这样执行一下,可以看到service3提交了。这里就不再分析源代码了。研究一下原因是:DAO没有标记事务,只是使用了当前的连接,因此当DAO异常,没有标记当前事务的roll-back。而异常在service2里边进行catch了,因此在service1中没有异常,因此对于整个事务来说,spring认为这个事务没有出现异常,因此事务正常提交。

在修改一下

service2
public void insertUser()  {
    try {
        demoService4.insertUser();
      userDao.insertUserError();//这里报错,字段超长
    }catch (Exception e) {
    e.printStackTrace();
  }
    
}
service4
public void insertUser()  {
    userDao.insertUserError();//这里报错,字段超长
    
}
这样再执行service1.getName还是和第一种情况一样,roll-back only了。

考虑到spring的事务传播机制,我们再对代码进行try catch时,应该多一些思考,事务传播往往和异常有着千丝万缕的关系。check exception不用多说了,主要是对于runtimeException,一定要慎重。 比如,在最内层catch异常,会对整个事务的原子性造成污染,即try的DAO可能执行失败了,但是外层方法的事务还是提交了。 当存在需要单独开辟事务的场景时,需要考虑其他的事务传播属性。

实际开发中,对于异常的处理更是一门学问,另找时间研究。

《Spring事务异常rollback-only深入探究》有1个想法

  1. 提交点:事务最外层方法的return处。
    方法内部出现异常,会有以下几种情况:
    1.最内层方法捕捉(即try块中没有嵌套事务),代码运行到提交点时:无异常,无rollback_only。
    2.非最内层方法捕捉,代码运行大提交点时:无异常,有rollback_only。
    3.不捕捉异常,则代码无法运行到提交点,事务不提交。

发表评论

邮箱地址不会被公开。 必填项已用*标注