您的位置:首页 > 编程语言 > Java开发

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

2016-03-10 16:49 411 查看
本文的读者希望能对数据库事务、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生效请参考文章:/article/3734276.html里面非常详细的描述了如何实现。如果对整个spring声明式事务感兴趣的也可以参考下面一篇文章去学习,该文章是从tx:annotation-driven如何生效讲起的,直到spring事务的各种传播机制是如何失效的:https://doanduyhai.wordpress.com/2011/11/20/spring-transactional-explained/

到此,我的分析结束,如果还有不明白的欢迎留言交流。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: