问题:当同一个类中的方法A调用方法B时,即使两个方法都打上了@Transactional注解,方法B的事务也不会生效。
原因:默认情况下Spring事务是基于代理的,也就意味着获取到的service对象是代理后的对象(class com.sun.proxy.$Proxy,基于接口的情况)。当外部调用该对象上的方法时,经过aop加上的事务逻辑后,最终会进入到目标对象(即原始的service对象)的方法逻辑,此时在方法内部再调用自己的另一个方法B,本质上就是在原始对象上进行调用,此时自然而然不会牵扯到任何aop的事务逻辑。
解决办法:由上而知,采用代理的方式肯定行不通,故而必须采用字节码提升的方式,直接修改类的字节码,为每个打上@Transactional注解的方法都加上事务逻辑。具体分两步走:
-
设置
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
,表示采用Aspectj 来对需要事务管理的方法进行字节码提升; -
首先引入
1 2 3 4 5
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.0</version> </dependency>
加入spring对Aspectj的支持。
而Aspectj weaving 的方式有两种:编译时和运行时。
编译时即在编译期间就完成了对类的字节码的修改。可以通过加入一个maven插件实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<!-- <goal>test-compile</goal> -->
</goals>
</execution>
</executions>
<configuration>
<complianceLevel>1.8</complianceLevel>
<forceAjcCompile>true</forceAjcCompile>
<weaveDirectories>
<weaveDirectory>${project.build.outputDirectory}</weaveDirectory>
</weaveDirectories>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
</plugin>
</plugin>
</build>
运行时即在class被加入到ClassLoader之前动态的修改类的字节码。
具体用到了LoadTimeWeaving,详情参考。
Foo类中方法A调用Bar类中的方法B是ok的,因为是在代理后的Bar的对象上进行操作,事务逻辑是存在的。但是要注意方法B的事务的传播级别,默认为REQUIRED,即如果存在了一个事务则直接使用,不存在才会创建一个新的事务。所以加入方法A的事务是READ-WRITE,方法B的事务是READ-ONLY,这时方法B会直接使用方法A的事务,会导致READ-ONLY失效。这时需要将方法B的传播级别改为REQUIRES_NEW。
建议: 强烈建议同一个类内部的事务方法之间不要进行相互调用,尽可能的重构此类代码,最好合并到一个事务方法中。这也是Spring官方推荐的。