那些年spring声明式事务@Transaction的坑

本文的读者希望能对数据库事务、spring事务、spring AOP相关概念、Java注解、Java反射、Java代理等技术有一定的了解。

作为开发人员,我相信大家都会遇到这样的一个业务场景:一个业务方法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生效请参考文章:http://jinnianshilongnian.iteye.com/blog/1487235里面非常详细的描述了如何实现。如果对整个spring声明式事务感兴趣的也可以参考下面一篇文章去学习,该文章是从tx:annotation-driven如何生效讲起的,直到spring事务的各种传播机制是如何失效的:https://doanduyhai.wordpress.com/2011/11/20/spring-transactional-explained/

       到此,我的分析结束,如果还有不明白的欢迎留言交流。



转载于:https://my.oschina.net/jerryyl/blog/634774

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值