问题:发现日志出现如下错误 org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
嵌套事务,传播级别设置PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
在项目中,一般我们都会使用默认的传播方式,这样无论外层事务和内层事务任何一个出现异常,那么所有的sql都不会执行。
在嵌套事务场景中,内层事务的sql和外层事务的sql会在外层事务结束时进行提交或回滚。如果内层事务抛出异常e,在内层事务结束时,spring会把事务标记为“rollback-only”。这时如果外层事务捕捉了异常e,那么外层事务方法还会继续执行代码,直到外层事务也结束时,spring发现事务已经被标记为“rollback-only”,但方法却正常执行完毕了,这时spring就会抛出异常。
异常日志错误代码示例
public class ServiceA {@Resourceprivate ServiceB b;@Transactionalpublic void a() {try {b.b()// 这里 滴滴滴,不应该catch,获取catch完,继续向上抛出。} catch (Exception ignore) {}}
}public class ServiceB {@Transactionalpublic void b() {throw new RuntimeException();}
}
当调用
a()
时,就会报出“rollback-only”
异常。
方案1: 外层事务不要catch,当然这是在满足需求的前提下哈。
public class ServiceA {@Resourceprivate ServiceB b;@Transactionalpublic void a() {//try {b.b()//} catch (Exception ignore) {//}}
}
方案2: 在外层事务的catch代码块中抛出e.
public class ServiceA {@Resourceprivate ServiceB b;@Transactionalpublic void a() {try {b.b()} catch (Exception ignore) {throw e;}}
}
方案3: 如果希望内层事务回滚,但不影响外层事务提交,需要将内层事务的传播方式指定为PROPAGATION_NESTED。
注:PROPAGATION_NESTED基于数据库savepoint实现的嵌套事务,外层事务的提交和回滚能够控制嵌内层事务,而内层事务报错时,可以返回原始savepoint,外层事务可以继续提交。
public class ServiceB {@Transactional(propagation = Propagation.NESTED)public void b() {throw new RuntimeException();}
}
public class ServiceA {@Resourceprivate ServiceB b;@Transactionalpublic void a() {// insert ab.b()}
}public class ServiceB {@Transactional(propagation = Propagation.REQUIRES_NEW)public void b() {//insert b;}
}
内事务异常
public class ServiceB {@Transactional(propagation = Propagation.REQUIRES_NEW)public void b() {//insert b;int i = 1/0;}
}
结果: 内外都回滚。
外事务异常
public class ServiceA {@Resourceprivate ServiceB b;@Transactionalpublic void a() {// insert ab.b()int i = 1/0;}
}
结果: 只有外事务回滚,内事务部回滚。
内事务异常
public class ServiceB {@Transactional(propagation = Propagation.NESTED)public void b() {//insert b;int i = 1/0;}
}
结果: 内外都回滚。
外事务异常
public class ServiceA {@Resourceprivate ServiceB b;@Transactionalpublic void a() {// insert ab.b()int i = 1/0;}
}
结果: 内外都回滚。
事务传播方式 | 说明 | 注意 |
---|---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是默认的传播方式 | 内外为一个大事务,无论是内事务异常,还是外事务异常,要么都成功要么都失败,是在外事务提交时一起提交的。内外都回滚。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行 | |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常 | |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起 | 内事务异常,内外都回滚;外事务异常,只有外事务回滚。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 | |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 | |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 | 内外都回滚。 |
参考: