在使用spring做事務管理時,很多人都會遇到這樣一段異常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:718) at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:475) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:270) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
出現上面問題的場景類似下面代碼這樣:
ITestAService:
package com.gigamore.platform.ac.service; import com.onlyou.framework.exception.BusinessException; public interface ITestAService { void testA() throws BusinessException; }
TestAService:
package com.gigamore.platform.ac.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.gigamore.platform.base.service.impl.BaseServiceImpl; import com.onlyou.framework.exception.BusinessException; @Service public class TestAService extends BaseServiceImpl implements ITestAService{ @Autowired private TestBService testBService; @Transactional public void testA(){ try{ testBService.testB(); }catch(BusinessException e){ logger.info(e.getMessage()); }catch(Exception e){ logger.info(e.getMessage()); } } }
TestBService:
package com.gigamore.platform.ac.service; import java.util.Date; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.gigamore.platform.ac.entity.LoanProjectEntity; import com.gigamore.platform.base.service.impl.BaseServiceImpl; import com.onlyou.framework.exception.BusinessException; @Service public class TestBService extends BaseServiceImpl{ @Transactional public void testB(){ LoanProjectEntity project = this.selectByPrimaryKey(LoanProjectEntity.class, "2c9483e748321d4601485e1714d31412"); project.setUpdDataTm(new Date()); this.update(project); throw new BusinessException("拋異常"); } }
測試用例:
@Autowired private ITestAService testAService; @Test public void testA() { testAService.testA(); }
testAService調用testBService的testB()方法,testB()方法里拋了一個BusinessException異常,但是testAService用try{}catch{}捕獲異常並不往上層拋了。
看起來好像沒什么問題,異常被捕獲了。其實不然,在testAService調用testBService的testB()方法時,會經過一次spring事務控制切面,事務切面里本身會對testBService的testB()方法進行異常捕獲: TransactionAspectSupport.invokeWithinTransaction
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // target invocation exception completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; }
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異常。
解決辦法:把TestBService的testB()方法的事務注解改成@Transactional(propagation = Propagation.NESTED),確實可以達到避免異常的效果。