问题出现
最近在重构之前同事的代码,完成重构后对代码进行调试的过程中,出现了服务调用异常的错误,而不是在项目中我们自定义的异常错误信息,所以怀疑这是框架层面的错误,而非业务代码上的错误。
问题排查
通过排查日志发现,具体的错误信息是Transaction rolled back because it has been marked as rollback-only,很明显是一个和事务相关的错误。且通过对业务逻辑中日志答应状况的排查,发现,业务正确执行到了最后,但是最后进行事务提交的时候进行了错误。开启spring事务后,当代码正确执行时,spring会在事务的最后进行一次事务提交,说明这是一个事务提交时的错误。
问题分析
通过对代码的查阅发现改业务逻辑上存在事务嵌套的情况。
通过对异常栈的仔细排查,发现,内层事务在一定场景下抛出了异常,但是却被外层事务用try–catch接住了。于是在内层事务异常的情况下,外层事务继续执行了。
原因解释
当spring开启事务时,如果不修改propagation的参数,则默认是propagation.REQUIRED。即如果有没有事务则新启一个事务,如果已经存在事务则加入这个事务。
当内层事务异常的情况下,如果是这种传播方式,正常来讲是需要回滚的,但是spring知识给内层事务做了一个rollback的标记。所以当内层事务抛出的异常被外层try-----catch时,外层事务正常执行,但在最后提交的时候发现,内层被标记了rollbck,所以就会抛出Transaction rolled back because it has been marked as rollback-only这个异常信息。
解决方案
原场景,内层事务的异常被外层事务捕获,内层被标记rollback,而外层提交,最后事务提交校验时抛出Transaction rolled back because it has been marked as rollback-only异常
@Transactional(rollbackFor = Exception.class)
public void methodA(){
insert();
System.out.println(1 / 0);
}
@Transactional(rollbackFor = Exception.class)
public void methodB(){
try{
methodA();
}catch(Exception e){
System.out.println("有异常");
}
}
可以分两种情况来解决这个异常
- 如果我们需要内层异常的情况下,回滚整个事务,可以让内层事务抛出的异常被外层事务的try----catch处理,再抛出新的异常,或者外层不通过try—catch处理这个异常。
- 当然如果内层事务没有复用过,只是在这个地方使用,直接把内层的事务去了,让他和外层合并成一个事务也能解决这个问题。
@Transactional(rollbackFor = Exception.class)
public void methodA(){
System.out.println(1 / 0);
}
@Transactional(rollbackFor = Exception.class)
public void methodB(){
try{
insert();
methodA();
}catch(Exception e){
throw new Exception("存在异常")
}
}
- 如果内层事务异常的情况下只回滚内层事务,修改内层事务的事务传播方式
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void methodA(){
System.out.println(1 / 0);
}
@Transactional(rollbackFor = Exception.class)
public void methodB(){
try{
insert();
methodA();
}catch(Exception e){
System.out.println("有异常");
}
}