那些年spring声明式事务@Transaction的坑
2016-03-10 16:49
411 查看
本文的读者希望能对数据库事务、spring事务、spring AOP相关概念、Java注解、Java反射、Java代理等技术有一定的了解。
作为开发人员,我相信大家都会遇到这样的一个业务场景:一个业务方法90%的逻辑都是在做查询,只有最后一部分才是对数据的更新。如果更新失败则业务回滚。常见的做法就是在该方法上加一个@Transaction的注解(本文只讲解spring的声明式事务的用法),或者在类上加@Transaction注解。如果将该方法拆成一个查询方法一个新增方法,在新增方法上加@Transaction事务还会生效吗?
这两种方式都可以实现该功能,但是如果说要优化这部分代码,将查询的业务从事务中剥离,缩短事务时间。修改代码如下:
如果有这个经历的应该知道,这两种方式@Transaction根本没有开启事务,也就是根本没有起作用。理想是美好的,现实是残酷的。But Why?
接下来请大家跟随我一步一步的来了解真正的@Transaction。
首先我们先来模拟下spring的@Transaction实现(此处很重要,请同学们一定要认真看)。众所周知spring的@Transaction是使用AOP等技术实现的,说白了就是Java的动态代理。而Java的动态代理有两种:jdk动态代理、cglib动态代理。
Jdk动态代理模拟@Transaction实现
运行结果如下:
发现没有?invoke只打印了一次,也就是说jdk动态代理只有在外部调用其方法时才会代理调用,自己调用自己的方法是不会走代理调用的。如果将@Transaction加在query()上是不会起作用的(请自行动手尝试)。那用cglib又如何呢?
Cglib代理模拟@Transaction实现
运行结果如下:
惊喜的发现跟jdk差别好大,自己调用自己的方法也是代理调用。那么也就是说spring的@Transaction如果是用cglib代理实现的话前面的优化代码是可行的,看下面的结果:
不用多说,这种形式肯定是cglib代理实现的。按照刚才的结论,这个@Transaction是会生效的,即该测试用例不会执行成功,因为我标注的是只读事务,只读事务中进行新增操作是会报错的,运行结果:
与预期的不一样。
下面来看spring是如何实现的
这两个类就类似我们模拟时的BookFacadeCglib类,我们首先来看CglibAopProxy类,其代理调用的核心实现如下:
1处我们可以理解成去扫描调用的方法上是否有@Transaction注解,即impl.add()的add()方法是否有注解,我们的例子中是没有注解,于是会走到分支2处,此处的注释很清楚,直接用目标对象调用add()方法,故add()中调用test()时也是目标对象在调用,而@Transaction的advise都是在代理对象上,所以自己调用自己的方法@Transaction是不生效的。按照这种说法是不是说在add()方法上加了@Transaction后test()方法上的注解就会生效,即测试用例报错,我们再来看看结果:
再来看jdk的动态代理:
也是一样的实现。
至于如何能够让自己调用自己时@Transaction生效请参考文章:/article/3734276.html里面非常详细的描述了如何实现。如果对整个spring声明式事务感兴趣的也可以参考下面一篇文章去学习,该文章是从tx:annotation-driven如何生效讲起的,直到spring事务的各种传播机制是如何失效的:https://doanduyhai.wordpress.com/2011/11/20/spring-transactional-explained/
到此,我的分析结束,如果还有不明白的欢迎留言交流。
作为开发人员,我相信大家都会遇到这样的一个业务场景:一个业务方法90%的逻辑都是在做查询,只有最后一部分才是对数据的更新。如果更新失败则业务回滚。常见的做法就是在该方法上加一个@Transaction的注解(本文只讲解spring的声明式事务的用法),或者在类上加@Transaction注解。如果将该方法拆成一个查询方法一个新增方法,在新增方法上加@Transaction事务还会生效吗?
public class Test { @Transaction public void 发优惠券() { 校验用户是否是注册用户(); 校验用户是否已经发过优惠券(); 新增优惠券(); } }
@Transaction public class Test { public void 发优惠券() { 校验用户是否是注册用户(); 校验用户是否已经发过优惠券(); 新增优惠券(); } }
这两种方式都可以实现该功能,但是如果说要优化这部分代码,将查询的业务从事务中剥离,缩短事务时间。修改代码如下:
public class Test { public void 发优惠券() { 校验用户是否是注册用户(); 校验用户是否已经发过优惠券(); 新增优惠券(); } @Transaction <span style="color:#ff0000;">public</span> void 新增优惠券() { 新增优惠券(); } }
public class Test { public void 发优惠券() { 校验用户是否是注册用户(); 校验用户是否已经发过优惠券(); 新增优惠券(); } @Transaction <span style="color:#ff0000;">private</span> void 新增优惠券() { 新增优惠券(); } }
如果有这个经历的应该知道,这两种方式@Transaction根本没有开启事务,也就是根本没有起作用。理想是美好的,现实是残酷的。But Why?
接下来请大家跟随我一步一步的来了解真正的@Transaction。
首先我们先来模拟下spring的@Transaction实现(此处很重要,请同学们一定要认真看)。众所周知spring的@Transaction是使用AOP等技术实现的,说白了就是Java的动态代理。而Java的动态代理有两种:jdk动态代理、cglib动态代理。
Jdk动态代理模拟@Transaction实现
public interface BookFacade { void addBook(); void query(); }
public class BookFacadeImpl implements BookFacade { @Transaction public void addBook() { query(); System.out.println("增加图书方法。。。"); } public void query() { System.out.println("查询是否可以增加图书"); } }
public class BookFacadeProxy implements InvocationHandler { private Object target; /** * 绑定委托对象并返回一个代理类 * * @param target * @return */ public Object bind(Object target) { this.target = target; // 取得代理对象 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); // 要绑定接口(这是一个缺陷,cglib弥补了这一缺陷) } /** * 调用方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("invoke"); Object result = null; Annotation annotation = target.getClass() .getDeclaredMethod(method.getName(), method.getParameterTypes()) .getAnnotation(Transaction.class); if (annotation == null) { result = method.invoke(target, args); return result; } else { System.out.println("事物开始"); result = method.invoke(target, args); System.out.println("事物结束"); return result; } } }
/** * 类Transaction.java的实现描述:模拟spring的transaction */ @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transaction { }
public class TestProxy { public static void main(String[] args) { BookFacadeProxy proxy = new BookFacadeProxy(); BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl()); bookProxy.addBook() } }
运行结果如下:
发现没有?invoke只打印了一次,也就是说jdk动态代理只有在外部调用其方法时才会代理调用,自己调用自己的方法是不会走代理调用的。如果将@Transaction加在query()上是不会起作用的(请自行动手尝试)。那用cglib又如何呢?
Cglib代理模拟@Transaction实现
public class BookFacadeImpl { @Transaction public void addBook() { query(); System.out.println("增加图书方法。。。"); } public void query() { System.out.println("查询是否可以增加图书"); } }
<pre name="code" class="java">public class BookFacadeCglib implements MethodInterceptor { private Object target; /** * 创建代理对象 * * @param target * @return */ public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { Annotation annotation = obj.getClass() .getSuperclass() .getDeclaredMethod(method.getName(), method.getParameterTypes()) .getAnnotation(Transaction.class); System.out.println("invoke"); if (annotation == null) { proxy.invoke(target, args); } else { System.out.println("事务开始"); proxy.invokeSuper(obj, args); System.out.println("事务结束"); } return null; } }
public class TestCglib { public static void main(String[] args) { BookFacadeCglib cglib = new BookFacadeCglib(); BookFacadeImpl bookCglib =(BookFacadeImpl) cglib.getInstance(new BookFacadeImpl()); bookCglib.addBook(); } }
运行结果如下:
惊喜的发现跟jdk差别好大,自己调用自己的方法也是代理调用。那么也就是说spring的@Transaction如果是用cglib代理实现的话前面的优化代码是可行的,看下面的结果:
不用多说,这种形式肯定是cglib代理实现的。按照刚才的结论,这个@Transaction是会生效的,即该测试用例不会执行成功,因为我标注的是只读事务,只读事务中进行新增操作是会报错的,运行结果:
与预期的不一样。
下面来看spring是如何实现的
这两个类就类似我们模拟时的BookFacadeCglib类,我们首先来看CglibAopProxy类,其代理调用的核心实现如下:
1处我们可以理解成去扫描调用的方法上是否有@Transaction注解,即impl.add()的add()方法是否有注解,我们的例子中是没有注解,于是会走到分支2处,此处的注释很清楚,直接用目标对象调用add()方法,故add()中调用test()时也是目标对象在调用,而@Transaction的advise都是在代理对象上,所以自己调用自己的方法@Transaction是不生效的。按照这种说法是不是说在add()方法上加了@Transaction后test()方法上的注解就会生效,即测试用例报错,我们再来看看结果:
再来看jdk的动态代理:
也是一样的实现。
至于如何能够让自己调用自己时@Transaction生效请参考文章:/article/3734276.html里面非常详细的描述了如何实现。如果对整个spring声明式事务感兴趣的也可以参考下面一篇文章去学习,该文章是从tx:annotation-driven如何生效讲起的,直到spring事务的各种传播机制是如何失效的:https://doanduyhai.wordpress.com/2011/11/20/spring-transactional-explained/
到此,我的分析结束,如果还有不明白的欢迎留言交流。
相关文章推荐
- java实现大整数的四则运算
- java參数传递机制浅析
- maven插件部署java项目到远程容器
- Java 自动装箱与拆箱(Autoboxing and unboxing)
- 《JAVA与模式》之状态模式
- FileWriter write 写文件缺失问题
- Spring集成ehcache
- java 数据类型
- Ant脚本编写
- Eclipse中集成Maven
- Eclipse修改右键点击新建的内容
- Spring 注解学习手札(六) 测试
- Java第二次作业
- javamail定时读取邮箱的未读邮件(imap)
- Java常用设计模式
- Spring集成ehcache
- Java第一次实验要求
- Java集合类详解
- Java实现用传统分治法解决矩阵相乘问题
- java中 == 与 equal 的区别