@Transactional
public void foo(){
    doSomthing();
    try{
        bar();
    }catch(ApiException e){
        logger.warn("call bar failed",e);
        // do something recovery work here
    }
    doSomethingElse();
    
}
@Transactional
public void bar(){
    // ...
    if(meetSomeCondition){
        throw new ApiException(...);
    }
    // ...
}

发现bar方法抛出了异常 即使foo中捕捉了该异常 仍导致foo方法最后回滚 并且还抛出如下的异常

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:720)

事故原因

However, in the case where an inner transaction scope sets the rollback-only marker, the outer transaction has not decided on the rollback itself, and so the rollback (silently triggered by the inner transaction scope) is unexpected. A corresponding UnexpectedRollbackException is thrown at that point. This is expected behavior so that the caller of a transaction can never be misled to assume that a commit was performed when it really was not. So if an inner transaction (of which the outer caller is not aware) silently marks a transaction as rollback-only, the outer caller still calls commit. The outer caller needs to receive an UnexpectedRollbackException to indicate clearly that a rollback was performed instead.

摘自: http://docs.spring.io/spring/...

发觉还真不好弄

  • 没有办法通过下面的方法来禁止回滚

@Transactional(noRollbackFor=ApiException.class)

因为如果是在foo方法中抛出的ApiException的话 还是需要回滚的

  • 在bar中显式指定事务传播方式 如

@Transactional(propagation=Propagation.REQUIRES_NEW)
public void bar()

即每次调用bar方法都会创建一个新事务 如foo方法创建了一个事务A 当调用bar方法时 会为其创建一个新的独立事务B 如果bar抛出异常 只会回滚B事务 不影响foo方法已有的事务A

但发觉也不可行 或许其他业务场景中 bar失败了就需要全部回滚呢?

所以最终还是决定新建一个方法 不含事务注解

public void anotherBarWithoutTransaction(){
    // ...
    if(meetSomeCondition){
        throw new ApiException(...);
    }
    // ...
}

@Transactional
public void bar(){ // 只是加了事务注解 
    anotherBarWithoutTransaction();
}

这样的话 如果有场景需要调用bar方法 但bar失败了的话不用会滚 可以直接调用anotherBarWithoutTransaction

但也不能为每个事务方法都准备一个不含事务的版本吧? 有没更好的解决方法呢?

还有一种解决方法

在foo中调用bar时使用编程式事务方式显式指定开启一个新事务 如下所示

@Autowired
private TransactionTemplate transactionTemplate;

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
   protected void doInTransactionWithoutResult(TransactionStatus status) {
       try {
           barService.bar();
       } catch (ApiException e) {
           logger.warn("call bar failed",e);
           status.setRollbackOnly(); // 注意一定要有此代码 否则仍会回滚外部事务
       }
   }
});

同时需要在spring配置文件中显式配置transactionTemplate

    <bean id="transactionTemplate"
          class="org.springframework.transaction.support.TransactionTemplate">
        <property name="propagationBehaviorName" value="PROPAGATION_REQUIRES_NEW"/>
        <property name="transactionManager" ref="txManager"/>
    </bean>

觉得这种方式优于为每个事务方法添加两个版本 如barWithTransactional和barWithoutTransactional

参考文档

http://docs.spring.io/autorep...

http://stackoverflow.com/ques...


zhuguowei2
825 声望26 粉丝