@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方法最後回滾 而且還拋出以下的異常html
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)
事故緣由java
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.spring
摘自: http://docs.spring.io/spring/...編程
發覺還真很差弄 ide
如code
沒有辦法經過下面的方法來禁止回滾orm
@Transactional(noRollbackFor=ApiException.class)
由於若是是在foo方法中拋出的ApiException的話 仍是須要回滾的htm
在bar中顯式指定事務傳播方式 如事務
@Transactional(propagation=Propagation.REQUIRES_NEW) public void bar()
即每次調用bar方法都會建立一個新事務 如foo方法建立了一個事務A 當調用bar方法時 會爲其建立一個新的獨立事務B 若是bar拋出異常 只會回滾B事務 不影響foo方法已有的事務Aci
但發覺也不可行 或許其餘業務場景中 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
參考文檔