在使用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方法写在testAService内部,即testA方法里用this.testB()调用,则不会出现以上的问题。因为testAService内方法testA调用方法testB,不会经过spring的事务切面。 由此引发出我用获得目标对象从而绕开spring事务切面的方法来避免该方法。
修改完之后的testAService的testA():
@Transactional
public void testA(){
try{
//testBService.testB();
AopTargetUtils.getTarget(testBService).testB();
}catch(BusinessException e){
logger.info(e.getMessage());
}catch(Exception e){
logger.info(e.getMessage());
}
}
AopTargetUtils:根据spring代理对象获取目标对象,spring没有提供相应的工具类,以下代码是网上找的,我加上了泛型约束,用反射获得代理类内部的目标对象。
package com.gigamore.platform.ac.service;
import java.lang.reflect.Field;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
public class AopTargetUtils {
/**
* 获取 目标对象
* @param proxy 代理对象
* @return
* @throws Exception
*/
public static <T> T getTarget(T proxy) throws Exception {
if(!AopUtils.isAopProxy(proxy)) {
return proxy;//不是代理对象
}
if(AopUtils.isJdkDynamicProxy(proxy)) {
return (T)getJdkDynamicProxyTargetObject(proxy);
} else { //cglib
return (T)getCglibProxyTargetObject(proxy);
}
}
private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
return target;
}
private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
return target;
}
}
这样,testAService调用testBService时,则类似Service内部的方法调用,不会经过spring事务切面,且此时testAService和testBService都在同一个事务里面,testBService的操作不会被回滚。
不过,这样会引发一个问题,我们不仅绕过了spring的事务切面,也绕过了spring的其他的AOP切面,如果有用spring做日志记录之类的AOP处理,在执行testBService的testB()方法时,则不会做日志记录的处理。
看了下评论里哥们说的用嵌套事务去处理这种情况,把TestBService
的testB()方法的事务注解改成@Transactional(propagation = Propagation.NESTED),确实可以达到避免异常的效果。推荐用嵌套事务这种方法。本文中的方法有很大的局限性,仅仅可作为解决问题的一个思路。