【spring】自我调用中transaction的常见问题

起因


最近有个实习生开发了一个job,这个job提测之后,测试那边很快反映有数据异常,看了下log发现已有几条异常信息,但我惊讶的是有数据不一致的问题。我浏览了一下代码,并没有发现什么问题,发现都有用Transactional和rollback声明(当时脑子有点短路,一时没有看出来),但直觉告诉我这种错误八成与事务处理不当有关。
既然如此只好debug一下,接着很快便知道了。

代码大概是这样的


示例代码

    @Transactional(rollbackFor = Exception.class)
public void methodMain() throws Exception {
int status = 5;
methodA(status);
try {
methodB(status); // 位置A
// memberOrderCopyService.methodB(status); // 位置B
} catch (Exception e) {
e.printStackTrace();
}
methodC(status);
}
@Transactional(rollbackFor = Exception.class)
public void methodA(Integer status) {
MemberOrder mo = memberOrderRepository.findOne(1L);
mo.setStatus(status);
memberOrderRepository.save(mo);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void methodB(Integer status) throws Exception {
MemberOrder mo = memberOrderRepository.findOne(2L);
mo.setStatus(status);
memberOrderRepository.save(mo);
th();
}
public void methodC(Integer status) throws Exception {
MemberOrder mo = memberOrderRepository.findOne(3L);
mo.setStatus(status);
memberOrderRepository.save(mo);
}
private void th() throws Exception {
throw new Exception("test");
}

如上,methodB()中有一个持久化的操作,在那之后有可能会抛异常(th()方法)。
实习生的想法是,methodB()加一个注解@Transactional(rollbackFor),想让methodB执行失败时回滚事务。
然而,这种是对spring aop不了解导致的错误。

解释


我对实习生说了这个错误之后,他却坚持这样是有效的。。没办法,只好以理服人。断点debug,翻源码。

如上图,可看到methodB()的调用栈,发现methodMaster()是经过拦截器后调用的(Cglib生成代理对象的情况下,AOP拦截和回调可在DynamicAdvisedInterceptor.intercept()方法中找到,如下)。

@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Class<?> targetClass = null;
Object target = null;
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// May be null. Get as late as possible to minimize the time we
// "own" the target, in case it comes from a pool...
target = getTarget();
if (target != null) {
targetClass = target.getClass();
}
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null) {
releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}

现在在来看methodB():

很明显,methodB是在methodMaster()中调用的,TransactionInterceptor并没有拦截,因此@Transactional是无效的。

现在来对比一下,切换启用位置B代码(使用memberOrderCopyService.methodB(status))的执行情况:

从上图可知,此时memberOrderCopyService.methodB()和原先的memberOrderService.methodMaster()都经过了TransactionInterceptor,然后通过代理invoke,这种是正确的,当有异常出现,事务也会正常回滚。

其实,这是一个简单的问题,通过推理也可以猜到,而且如果内部每个方法都用拦截器,会是一个很大的性能问题。


FastClassBySpringCGLIB
EnhancerBySpringCGLIB
其实如果用Aspect也可以实现类似Transactional的功能,
下次有空写一个Aspect 和 Transactional的对比。

参考