一、背景
spring的注解事物没有生效,异常数据没有回滚。
二、具体现象
同一个类中有多个方法,A方法没有开启事物,B方法通过注解开启事物,B方法的事物注解没有生效。代码如下:
package com.test.transcation; import org.springframework.transaction.annotation.Transactional; /** * Created by shaobo on 2018/4/9. */ public class Insert { public void a(){ this.b(); } @Transactional public void b(){ /** * 一通数据库操作 */ throw new RuntimeException(); } }
执行方法a(),方法b()中的数据成功更新到了数据库中,预期结果为数据回滚。
三、分析
我们知道spring的事物是通过cglib来生成动态代理的。先来看JDK的动态代理。
package com.test.proxy; /** * 接口 */ public interface UserInterface { void update(); void complex(); }
package com.test.proxy; /** * 实现 */ public class UserService implements UserInterface { @Override public void update() { System.out.println("userDao.update()"); } @Override public void complex(){ System.out.println("begin complex()"); this.update(); System.out.println("end complex()"); } }
package com.test.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * InvocationHandler */ public class JDKProxy implements InvocationHandler { private Object target; public void bind(UserInterface userInterface){ target = userInterface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before Method Invoke " + method.getName()); Object object = method.invoke(target,args); System.out.println("After Method Invoke " + method.getName()); return object; } }
package com.test.proxy; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { UserService userService = new UserService(); JDKProxy jdkProxy = new JDKProxy(); jdkProxy.bind(userService); UserInterface userInterface = (UserInterface)Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),jdkProxy); userInterface.complex(); } }
执行结果:我们通过debug方式执行关键一下被代理对象
我们可以看到this对象为实际对象,所以update方法并没有被拦截。
接下来我们看一下cglib,
package com.test.cglib; /** * 代理对象 */ public class UserDao { public void update() { System.out.println("userDao.update()"); } public void complex() { System.out.println("begin complex()"); this.update(); System.out.println("end complex()"); } }
package com.test.cglib; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 代理类 */ public class DaoProxy implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("Before Method Invoke " + method.getName()); methodProxy.invokeSuper(o, objects); System.out.println("After Method Invoke " + method.getName()); return o; } }
package com.test.cglib; import net.sf.cglib.proxy.Enhancer; public class Test { public static void main(String[] args) { DaoProxy daoProxy = new DaoProxy(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserDao.class); enhancer.setCallback(daoProxy); UserDao dao = (UserDao)enhancer.create(); dao.complex(); } }
执行结果如下
我们可以发现cglib中的this指向代理对象,所以也会执行拦截方法。
spring aop的模型大致是这样的:
这样会导致methodB()并不能被通知到。我想如果如下图这样的话就不会出现这种问题,但spring这样这样设计肯定有其理由,需要后续继续研究。
四、解决办法
知道了原因就好解决了,方法有如下两种。
1、不要使用spring 中嵌套aop,将这种嵌套放在两个类中(推荐)。
2、((UserInterface)AopContext.currentProxy()).update(),通过此方法获得代理对象直接调用。
踩过的坑都是流过的泪。