Issue
希望在项目中使用多个事务管理不同代码块,子事务间隔离互不影响,父事务回滚子事务也全部回滚,但是却报错:“org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only”。
Root Cause
因为在一个方法中使用了多个事务,在外层方法上使用了注解@Transctional并在方法内手动开启了事务。spring默认事务传播级别为:PROPAGATION_REQUIRED
,子事务的sql和父事务的sql会在外层事务结束时进行提交或回滚。如果内层事务抛出异常e,在内层事务结束时,spring会把事务标记为“rollback-only”。这时如果外层事务捕捉了异常e,那么外层事务方法还会继续执行代码,直到外层事务也结束时,spring发现事务已经被标记为“rollback-only”,但方法却正常执行完毕了,这时spring就会抛出“org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only”。
Dev Design
手动开启事务时,使用PROPAGATION_NESTED
传播级别,代码块使用try-catch处理异常。PROPAGATION_NESTED
基于数据库savepoint实现的嵌套事务,父事务的提交和回滚能够控制嵌内层事务,而子事务报错时,可以返回原始savepoint,父事务可以继续提交。
@Transactional(rollbackFor = Exception.class)
public void doAll() {
doOtherThing();
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
TransactionStatus status = txManager.getTransaction(def);
try{
// do something
doSomeThing();
// 提交事务
txManager.commit(status);
} catch (Exception e) {
txManager.rollback(status);
}
doAnotherThing();
}
理解事务的传播级别
@see org.springframework.transaction.annotation.Propagation
事务传播方式 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是默认的传播方式 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |