Spring 事务失效场景
1. 方法是 private 私有的
Spring AOP 只拦截 public 方法,所以 private 方法 AOP 并不会拦截
解决方案
方案1:方法改为 public
方案2:事务的 AOP 的实现改为 AspectJ,AspectJ 可以拦截私有方法、构造方法、字段访问等
Spring 的 AOP 改为 AspectJ 和 事务管理器使用 AspectJ 这是两个不同的配置,下面是事务管理器使用 AspectJ 的示例
-
引入依赖
<!-- AspectJ 事务支持 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency> -
配置事务管理器的代理模式为 AspectJ
@Configuration @EnableTransactionManagement(mode = AdviceMode.ASPECTJ) // 关键! public class TransactionConfig { @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } } -
阿斯蒂芬
@Service public class UserService { @Transactional // 即使是 private 方法,AspectJ 也能生效! private void updatePrivate() { // 数据库操作 } public void doSomething() { updatePrivate(); // 事务生效 } } -
配置织入方式(加载时织入 和 编译时织入 两种方式,这里是通过 maven 插件的编译时织入)
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.14.0</version> <configuration> <complianceLevel>11</complianceLevel> <source>11</source> <target>11</target> <showWeaveInfo>true</showWeaveInfo> <Xlint>ignore</Xlint> <encoding>UTF-8</encoding> </configuration> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin>
2. 方法被 final 或 static 修饰
JDK 不允许子类重写,所以 AOP 会失效
3. 自调用(调用本类方法)
如下示例,事务会失效
public class UserService {
public void updateUser() {
this.updateUserStatus(); // 调用本来中带事务的方法
}
@Transactional
public void updateUserStatus() {
// ...
}
}
// Controller
userService.updateUser(); // 事务失效
因为事务是通过 AOP 实现的,使用的时候注入的 UserService 其实是代理对象
代理对象只会重写 updateUserStatus() 方法,不会重写 updateUser() 方法
controller 中调用 updateUser() 方法,会调用目标对象的方法(回到了目标对象),所以 updateUserStatus() 也会是目标对象的方法
解决方案
方案1:自我注入,不直接调用本类的方法,但这会形成循环依赖
public class UserService {
@Resource
private UserService self; // 自我注入
public void updateUser() {
self.updateUserStatus(); // 调用注入的 UserService 的方法
}
@Transactional
public void updateUserStatus() {
// ...
}
}
方案2:AopContext.currentProxy() 获取代理对象
方案3:方法抽出来,抽到另一个类中,不要直接调用本类的方法
4. 异常处理不当
- 捕获异常未重新抛出
- 没有指定
rollBackFor,默认就是RuntimeException,抛出的异常不是RuntimeException或其子类
@Transactional
public void update() {
try {
// 数据库操作
} catch (Exception e) {
// 捕获异常未抛出,事务不会回滚
}
}
解决方案:确保异常是 RuntimeException 类型(或者配置 rollBackFor 指定异常),如果捕获了一场,要抛出来
5. 数据库不支持事务
这个比较少见,也算一种场景,比如 Mysql 的 MyISAM 存储引擎就不支持事务
6. 事务传播行为配置不当
比如使用 Propagation.NEVER 传播方式

浙公网安备 33010602011771号