用spring目标对象处理Transaction rolled back because it has been marked as rollback-only
在使用spring做事务管理时,很多人都会遇到这样一段异常:
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)
出现上面问题的场景类似下面代码这样:
ITestAService:
package com.gigamore.platform.ac.service; import com.onlyou.framework.exception.BusinessException; public interface ITestAService { void testA() throws BusinessException; }
TestAService:
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:
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 }
测试用例:
1 @Autowired 2 private ITestAService testAService; 3 @Test 4 public void testA() { 5 testAService.testA(); 6 }
testAService调用testBService的testB()方法,testB()方法里抛了一个BusinessException异常,但是testAService用try{}catch{}捕获异常并不往上层抛了。
看起来好像没什么问题,异常被捕获了。其实不然,在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异常。
解决办法:把TestBService的testB()方法的事务注解改成@Transactional(propagation = Propagation.NESTED),确实可以达到避免异常的效果。
Spring中七种Propagation类的事务属性详解:
REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
注意:这个配置将影响数据存储,必须根据情况选择