让Spring事务支持同一个类的内部调用

问题:当同一个类中的方法A调用方法B时,即使两个方法都打上了@Transactional注解,方法B的事务也不会生效。

原因:默认情况下Spring事务是基于代理的,也就意味着获取到的service对象是代理后的对象(class com.sun.proxy.$Proxy,基于接口的情况)。当外部调用该对象上的方法时,经过aop加上的事务逻辑后,最终会进入到目标对象(即原始的service对象)的方法逻辑,此时在方法内部再调用自己的另一个方法B,本质上就是在原始对象上进行调用,此时自然而然不会牵扯到任何aop的事务逻辑。

解决办法:由上而知,采用代理的方式肯定行不通,故而必须采用字节码提升的方式,直接修改类的字节码,为每个打上@Transactional注解的方法都加上事务逻辑。具体分两步走:

       1. 设置 @EnableTransactionManagement(mode = AdviceMode.ASPECTJ) ,表示采用Aspectj 来对需要事务管理的方法进行字节码提升。

       2. 首先引入           


    org.springframework
    spring-aspects
    5.3.0

      加入spring对Aspectj的支持。

      而Aspectj weaving 的方式有两种:编译时和运行时。

      编译时即在编译期间就完成了对类的字节码的修改。可以通过加入一个maven插件实现:


    
        
            org.codehaus.mojo
            aspectj-maven-plugin
            1.6
            
                
                    
                        compile
                        
                    
                
            
            
                1.8
                true
                
                    ${project.build.outputDirectory}
                
                
                    
                        org.springframework
                        spring-aspects
                    
                
            
        
    

   运行时即在class被加入到ClassLoader之前动态的修改类的字节码。

   具体用到了LoadTimeWeaving,详情参考Spring官方文档  https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-aj-ltw。

 

补充:Foo类中方法A调用Bar类中的方法B是ok的,因为是在代理后的Bar的对象上进行操作,事务逻辑是存在的。但是要注意方法B的事务的传播级别,默认为REQUIRED,即如果存在了一个事务则直接使用,不存在才会创建一个新的事务。所以加入方法A的事务是READ-WRITE,方法B的事务是READ-ONLY,这时方法B会直接使用方法A的事务,会导致READ-ONLY失效。这时需要将方法B的传播级别改为REQUIRES_NEW。

 

建议: 强烈建议同一个类内部的事务方法之间不要进行相互调用,尽可能的重构此类代码,最好合并到一个事务方法中。这也是Spring官方推荐的。

你可能感兴趣的:(Spring,事务)