[问题]编程
Spring的声明式事务,我想就不用多介绍了吧,一句话“自从用了Spring AOP啊,事务管理真轻松啊,真轻松;事务管理代码没有了,脑不酸了,手不痛了,一口气全配上了事务;轻量级,测试起来也简单,嘿!”。无论从哪一个角度看,轻量级声明式事务都是一件解放生产力的大好事。因此,咱们“一直用它”。测试
不过,最近的一个项目里,却碰到了一个事务管理上的问题:有一个服务类,其一个声明了事务的方法,里面作了三次插入SQL操做,可是在后面出错回滚时,却发现前面插入成功了,也是说,这个声明了事务的方法,实际上并无真正启动事务!怎么回事呢?难道Spring的声明式事务失效了?ui
[探幽]代理
其实之前也会碰到有人说,Spring的事务配置不起做用,可是根据第一反应和以往经验,我总会告诉他,确定是你的配置有问题啦;因此这一次,我想也不会例外,大概是把事务注解配在了接口上而不是实现方法上,或者,若是是用XML声明方式的话,极可能是切入点的表达式没有配对。对象
不过,在检查了他们的配置后,却发现没有配置问题,该起事务的实现方法上,用了@Transactional事务注解声明,XML里也配了注解驱动<tx:annotation-driven .../>,配置很正确啊,怎么会不起做用?接口
我很纳闷,因而往下问:事务
问1:其余方法有这种状况么?开发
答1:没有。io
问2:这个方法有什么特别的么(如下简称方法B)?后台
答2:就是调后台插了三条记录啊,没啥特别的。
问3:这个方法是从Web层直接调用的吧?
答3:不是,是这个Service类(如下简称ServiceA)的另一个方法调过来的(如下简称方法A)。
问4:哦,那个调用它的方法配了事务么(问题可能在这了)?
答4:没有。
问5:那WEB层的Action(用的是Struts2),调用的是没有声明事务的方法A,方法A再调用声明了事务的方法B?
答5:对的。
问6:你直接在方法A上加上事务声明看看
答6:好。。。
看来可能找到问题所在了,因而把@Transactional也加在方法A上,启动项目测试,结果是:事务正常生效,方法A和方法B都在一个事务里了。
好了,如今总结一下现象:
一、ServiceA类为Web层的Action服务
二、Action调用了ServiceA的方法A,而方法A没有声明事务(缘由是方法A自己比较耗时而又不须要事务)
三、ServiceA的方法A调用了本身的方法B,而方法B声明了事务,可是方法B的事务声明在这种状况失效了。
四、若是在方法A上也声明事务,则在Action调用方法A时,事务生效,而方法B则自动参与了这个事务。
我让他先把A也加上事务声明,决定回来本身再测一下。
这个问题,表面上是事务声明失效的问题,实质上极可能是Spring的AOP机制实现角度的问题。我想到好久之前研究Spring的AOP实现时发现的一个现象:对于以Cglib方式加强的AOP目标类,会建立两个对象,一个事Bean实例自己,一个是Cglib加强代理对象,而不只仅是只有后者。我曾经疑惑过这一点,但当时没有再仔细探究下去。
咱们知道,Spring的AOP实现方式有两种:一、Java代理方式;二、Cglib动态加强方式,这两种方式在Spring中是能够无缝自由切换的。Java代理方式的优势是不依赖第三方jar包,缺点是不能代理类,只能代理接口。
Spring经过AopProxy接口,抽象了这两种实现,实现了一致的AOP方式:
如今看来,这种抽象一样带了一个缺陷,那就是抹杀了Cglib可以直接建立普通类的加强子类的能力,Spring至关于把Cglib动态生成的子类,当普通的代理类了,这也是为何会建立两个对象的缘由。下图显示了Spring的AOP代理类的实际调用过程:
所以,从上面的分析能够看出,methodB没有被AopProxy通知到,致使最终结果是:被Spring的AOP加强的类,在同一个类的内部方法调用时,其被调用方法上的加强通知将不起做用。
而这种结果,会形成什么影响呢:
1:内部调用时,被调用方法的事务声明将不起做用
2:换句话说,你在某个方法上声明它须要事务的时候,若是这个类还有其余开发者,你将不能保证这个方法真的会在事务环境中
3:再换句话说,Spring的事务传播策略在内部方法调用时将不起做用。无论你但愿某个方法须要单独事务,是RequiresNew,仍是要嵌套事务,要Nested,等等,通通不起做用。
4:不只仅是事务通知,全部你本身利用Spring实现的AOP通知,都会受到一样限制。。。。
[解难]
问题的缘由已经找到,其实,我理想中的AOP实现,应该是下面这样:
只要一个Cglib加强对象就好,对于Java代理方式,个人选择是坚决果断的抛弃。
至于前面的事务问题,只要避开Spring目前的AOP实现上的限制,要么都声明要事务,要么分开成两个类,要么直接在方法里使用编程式事务,那么一切OK。