1、spring AOP 动态代理机制导致的spring事务失效的问题原理分析
先上两张图,看懂图的同学可以不继续往下看了。
这里说一下问题。springMVC的service层中一个类提供了多个方法,a()、b()、c()、d()。其中方法a上未显示声明事务(@Transactional),而方法b()、c()、d()都显示声明了事务。并且spring的注解扫描和bean的实例化过程都没有问题(排除其他问题产生的影响)。
此时controller层调用使用此service对象调用方法a(),其中处理逻辑是 a()->b()->c()->d(),有这样的调用关系。结果就是这个从方法a开始的调用没有添加上事务。
是的,乍一看有点奇怪,仔细分析一下,就清晰了。
这还得从spring AOP的实现原理说起,spring AOP是基于动态代理实现的,分jdk和cglib两种,简单来说,jdk只对接口代理,cglib可以对普通类代理。事务的实现是通过代理类(proxy)来实现的,当一个添加注解的类被动态代理后,所有调用这个类对象的方式都是先执行代理类的开启事务(如果这个方法添加了事务),再进入被代理对象执行相应的方法。但是在被代理对象中调用本对象中的方法时,不会再去通过代理类来执行(如上图1),由于事务是通过代理类完成的,所以如果父调用没有开启,后面的方法调用是不会再开启事务的。上述例子中方法a没有声明事务,所以代理类中没有对a方法开启事务,进入被代理系统后,继而调用方法b、c、d,虽然方法b、c、d上有事务注解,但因为是原代理类内部方法调用,并没有返回代理类去开启事务。故事务“失效”。
图二是理想的用法,可以在被代理类中通过AopContext.currentProxy();得到当前类的代理类,然后继续从代理类执行对应的有事务的方法。
记录一个额外的问题,有时发现事务不起作用,可能有如下原因。
一般的配置方法是让Spring管理除了Controller注解以外注解,而让SpringMVC单纯管理Controller注解。
也就是说Spring有一个配置文件,里面配置成扫描非Controller的bean,SpringMVC有一个配置文件,里面只扫描Controller。
这样就形成了两个上下文,即Spring的上下文和SpringMVC的上下文,他们分别管理着不同的俩堆bean。
这个时候你在Spring的配置文件里加了一句tx:annoation-driven,其实是告诉Spring,你管理的这些bean里面有可能会出现需要事务支持的。
然后在Spring管理范围的某个bean上加了个注解@transactional,其实是用来帮助Spring识别这个bean是需要事务管理的。
同样你在Spring的配置文件里加了tx:annoation-driven,SpringMVC是不关心的,他只关心你给他配置了什么以及他管理的bean上有什么注解
2、我的理解
1、根据默认的事务传播机制Propagation.REQUIRED,配置了事务的方法,没有事务就创建,已有则沿用。
2、根据第一条,只要进入service层第一个方法配置了事务,自己调用内部方法或者其他方法都会沿用这个已经开启的事务,不管被调用的方法是否配置了事务。
问题来了,
a)、如果第一个方法没有事务,它调用的其他方法有事务配置,结果表明调用的其他方法都是没有事务生效的。
b)、结果表明,不管第一个方法是否配置事务,都会经过代理类,代理类里面判断当前方法是否有事务拦截(是否配置事务),
如果没有,直接通过反射invoke调用方法;
如果有,也是通过反射调用,但是在反射时候带上事务的拦截,也就实现了事务管理。
c)、结果表明,如果是上面说的第一个方法没有事务但是自己调用了有事务的方法比如A(){b()},那么实际上是通过反射调用了A(),并且调用这个A()方法的时候没有检测到事务拦截,直接通过clazz.invoke(A)来执行A()方法,因为b()方法在A里面,会直接也被clazz.invoke执行了,因此b()方法实际上是没有经过“service代理类在执行第一个方法时会检测这个方法是否配置事务从而为它在执行invoke的时候加上事务拦截”这个步骤的!
d)、解决办法:
- 需要事务的方法必须是进来service调用的第一个方法,或者在第一个方法上加上事务,再调用其他需要事务的方法。
补充
如下service实例:
class service{
void a(){
b();
c();
}
void b(){
//操作数据库,保存b字段
}
void c(){
//操作数据库,保存c字段
}
}
现在从controller层调用a方法,要求保存b字段和保存c字段互不干扰,也就是a方法不加事务,但是c方法要加事务,怎么实现呢?
参考:service中注入自己的调用,通过
void a(){
b();
service.c();
}
来给c()方法加上代理走事务。