在使用spring作事務管理時,不少人都會遇到這樣一段異常:java
1 org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 2 at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:718) 3 at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:475) 4 at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:270) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94) 5 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96) 6 at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260) 7 at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
出現上面問題的場景相似下面代碼這樣:spring
ITestAService:測試
package com.gigamore.platform.ac.service; import com.onlyou.framework.exception.BusinessException; public interface ITestAService { void testA() throws BusinessException; }
TestAService:this
1 package com.gigamore.platform.ac.service; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.stereotype.Service; 5 import org.springframework.transaction.annotation.Transactional; 6 7 import com.gigamore.platform.base.service.impl.BaseServiceImpl; 8 import com.onlyou.framework.exception.BusinessException; 9 @Service 10 public class TestAService extends BaseServiceImpl implements ITestAService{ 11 @Autowired 12 private TestBService testBService; 13 @Transactional 14 public void testA(){ 15 try{ 16 testBService.testB(); 17 }catch(BusinessException e){ 18 logger.info(e.getMessage()); 19 }catch(Exception e){ 20 logger.info(e.getMessage()); 21 } 22 } 23 }
TestBService:spa
1 package com.gigamore.platform.ac.service; 2 3 import java.util.Date; 4 5 import org.springframework.stereotype.Service; 6 import org.springframework.transaction.annotation.Propagation; 7 import org.springframework.transaction.annotation.Transactional; 8 9 import com.gigamore.platform.ac.entity.LoanProjectEntity; 10 import com.gigamore.platform.base.service.impl.BaseServiceImpl; 11 import com.onlyou.framework.exception.BusinessException; 12 @Service 13 public class TestBService extends BaseServiceImpl{ 14 @Transactional 15 public void testB(){ 16 LoanProjectEntity project = this.selectByPrimaryKey(LoanProjectEntity.class, "2c9483e748321d4601485e1714d31412"); 17 project.setUpdDataTm(new Date()); 18 this.update(project); 19 throw new BusinessException("拋異常"); 20 } 21 }
測試用例:code
1 @Autowired 2 private ITestAService testAService; 3 @Test 4 public void testA() { 5 testAService.testA(); 6 }
testAService調用testBService的testB()方法,testB()方法裏拋了一個BusinessException異常,可是testAService用try{}catch{}捕獲異常並不往上層拋了。orm
看起來好像沒什麼問題,異常被捕獲了。其實否則,在testAService調用testBService的testB()方法時,會通過一次spring事務控制切面,事務切面裏自己會對testBService的testB()方法進行異常捕獲: TransactionAspectSupport.invokeWithinTransaction對象
1 if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { 2 // Standard transaction demarcation with getTransaction and commit/rollback calls. 3 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); 4 Object retVal = null; 5 try { 6 // This is an around advice: Invoke the next interceptor in the chain. 7 // This will normally result in a target object being invoked. 8 retVal = invocation.proceedWithInvocation(); 9 } 10 catch (Throwable ex) { 11 // target invocation exception 12 completeTransactionAfterThrowing(txInfo, ex); 13 throw ex; 14 } 15 finally { 16 cleanupTransactionInfo(txInfo); 17 } 18 commitTransactionAfterReturning(txInfo); 19 return retVal; 20 }
completeTransactionAfterThrowing(txInfo, ex)裏面作了txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()),事務管理器作rollback, 把事務設置成rollback-only。 以上是testBService外層包裝的事務切面作的事情。當testAService的testA()方法執行完,此時執行到testAService外層包裝的事務切面,因爲testA()方法執行過程沒有拋出異常,因此事務正常提交,即執行的是commitTransactionAfterReturning(txInfo),事務對象txInfo對應的事務管理器進行提交事務,但事務已被設置爲rollback-only,故spring對外拋出了Transaction rolled back because it has been marked as rollback-only異常。blog
解決辦法:把TestBService的testB()方法的事務註解改爲@Transactional(propagation = Propagation.NESTED),確實能夠達到避免異常的效果。事務
Spring中七種Propagation類的事務屬性詳解:
REQUIRED:支持當前事務,若是當前沒有事務,就新建一個事務。這是最多見的選擇。
SUPPORTS:支持當前事務,若是當前沒有事務,就以非事務方式執行。
MANDATORY:支持當前事務,若是當前沒有事務,就拋出異常。
REQUIRES_NEW:新建事務,若是當前存在事務,把當前事務掛起。
NOT_SUPPORTED:以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。
NEVER:以非事務方式執行,若是當前存在事務,則拋出異常。
NESTED:支持當前事務,若是當前事務存在,則執行一個嵌套事務,若是當前沒有事務,就新建一個事務。
注意:這個配置將影響數據存儲,必須根據狀況選擇