本文的读者希望能对数据库事务、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动态代理。
-
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又如何呢?
-
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/
到此,我的分析结束,如果还有不明白的欢迎留言交流。