transaction rolled back because it has been marked as rollback-only
简略总结=>
发生场景:try-catch多业务场景
发生原因:业务嵌套,事务管理混乱,外层业务与内层业务抛出异常节点与回滚节点不一致。
解决方式:修改业务的事务嵌套关系为[ NESTED ],保证内层业务的异常不会设置外层的回滚
Spring中的事务拥有多种隔离机制,类似默认的REQUIRED,REQUIRES_NEW。
这个问题会发生的原因则是事务的隔离机制问题。
业务A是外层业务,调用了业务B内层业务。并且try-catch了它
类似
public void testA(){
try{
testB();
}catch(){
}
}
如果这是一个有事务的场景,那么如果testB()方法抛出异常,而异常被testA()的
try-catch捕获了,那么spring在执行testA()的事务commit时就会抛出
transaction rolled back because it has been marked as rollback-only的异常。
因为内层业务在发生异常的时候,标注了改事务是需要回滚的,但是外层捕获了异常后继续执行,方法完成以后提交便出错了,无法提交需要被回滚的内容。
解决方法则是修改testA()与testB()之间的事务隔离,将本来继承或者加入testA()事务的testB()的事务隔离级别设置为 NESTED;
@Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
nested的意思是嵌套事务,如果外层有事务那么嵌套一个事务在其中,如果不存在事务则新建一个事务。参考地址:NESTED
如果使用了这个级别,那么testB()会嵌套在testA()里,遇到异常时会回滚到开始执行testB()的数据,将原来的整体事务回滚修改为局部事务回滚,这样的话在testA()捕获了异常以后也不会影响testA()的其他业务执行。
另话:
除了NESTED外还有REQUIRES_NEW这个级别,它是新建一个新的事务,和NESTED的区别就是,REQUIRES_NEW是两个事务,同级,A跟B各自创建了一个事务,NESTED是父子级,B的事务在A里面。为什么这里选用了NESTED的级别没用REQUIRES_NEW呢,是因为我在做业务的时候发现使用REQUIRES_NEW包住的代码无法写入数据库,因为外层testA()的事务没有结束,数据库的表被testA()锁住了,导致无法写入,NESTED则没有这种问题。