一、背景
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(),通過此方法獲得代理對象直接調用。
踩過的坑都是流過的淚。