@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
参考文档