Spring 事务失效的核心原因是 AOP 动态代理未触发 、事务配置不合法 、异常未被感知 或 底层不支持事务。以下是最常见的失效场景:
一、最常见:内部方法调用(同一类内无代理转发)
场景描述
同一个类中,无事务的方法 A 调用有事务的方法 B,此时方法 B 的事务会失效。
失效原因
Spring 事务基于动态代理(JDK/CGLIB),只有通过 代理对象 调用目标方法时,AOP 切面才会触发事务逻辑。而同一类内的方法调用是 this.方法()(目标对象自身调用),跳过了代理对象,事务切面无法介入。
示例代码
typescript
@Service
public class UserService {
// 无事务方法
public void updateUserWithoutTx() {
// 内部调用有事务方法(this 调用,无代理)
this.updateUserWithTx(); // 事务失效
}
// 有事务方法
@Transactional
public void updateUserWithTx() {
// 数据库操作(无事务支持)
userMapper.update();
}
}
解决方案
- 拆分方法到不同类(通过依赖注入调用代理对象);
- 自注入代理对象(
@Autowired private UserService thisProxy;,需开启exposeProxy = true); - 通过
AopContext.currentProxy()获取代理对象调用。
二、方法访问权限非 public
场景描述
@Transactional 注解加在 非 public 方法(private/protected/default)上。
失效原因
Spring 事务拦截器(TransactionInterceptor)的源码中,会判断目标方法的访问权限:仅 public 方法会触发事务 。非 public 方法的 Method.getModifiers() 不满足 Modifier.isPublic(modifiers),事务切面直接跳过。
示例代码
typescript
@Service
public class UserService {
// 错误:private 方法加事务(失效)
@Transactional
private void updateUser() {
userMapper.update();
}
}
解决方案
将事务方法改为 public 访问权限。
三、异常被手动捕获(未抛出到代理层)
场景描述
事务方法内部捕获了异常(try-catch),且未重新抛出异常。
失效原因
Spring 事务默认通过 感知未捕获的异常 来触发回滚(RuntimeException/Error)。如果异常被手动捕获且未抛出,代理层无法感知异常,会认为方法执行成功,直接提交事务。
示例代码
java
@Service
public class UserService {
@Transactional
public void updateUser() {
try {
userMapper.update();
int i = 1 / 0; // 抛出 RuntimeException
} catch (Exception e) {
// 错误:捕获异常但未抛出,事务无法感知
log.error("更新失败", e);
}
}
}
解决方案
- 捕获后重新抛出异常(
throw new RuntimeException(e)); - 手动触发回滚(
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly())。
四、抛出非检查异常(默认不回滚的异常)
场景描述
事务方法抛出 检查异常 (如 IOException、SQLException),且未通过 rollbackFor 配置指定回滚异常。
失效原因
Spring 事务默认只回滚 RuntimeException 和 Error(非检查异常)。对于检查异常(编译时必须捕获的异常),默认不会触发回滚,事务会正常提交。
示例代码
java
@Service
public class UserService {
// 错误:抛出 IOException(检查异常),默认不回滚
@Transactional
public void updateUser() throws IOException {
userMapper.update();
throw new IOException("文件读取失败");
}
}
解决方案
通过 rollbackFor 显式指定回滚异常:
java
@Transactional(rollbackFor = Exception.class) // 捕获所有异常回滚
public void updateUser() throws IOException {
// ...
}
五、目标类 / 方法为 final(无法生成代理)
场景描述
- 事务所在类是
final修饰的; - 事务方法是
final修饰的。
失效原因
Spring 动态代理的两种实现:
- JDK 动态代理:基于接口,目标类需实现接口(final 类无法被代理);
- CGLIB 动态代理:基于类继承,final 类无法被继承,final 方法无法被重写。
无论哪种方式,final 修饰都会导致代理对象无法生成或无法重写目标方法,事务切面无法切入。
示例代码
java
// 错误:final 类(无法生成 CGLIB 代理)
@Service
public final class UserService {
// 错误:final 方法(无法被 CGLIB 重写)
@Transactional
public final void updateUser() {
userMapper.update();
}
}
解决方案
移除 final 修饰符(类和事务方法均不能为 final)。
六、Bean 未被 Spring 容器管理
场景描述
事务所在的类未通过 @Service、@Component 等注解交给 Spring 管理,而是手动 new 出来的对象。
失效原因
Spring 事务依赖容器生成的 代理 Bean ,手动 new 的对象是原始目标对象,未被 Spring 增强(无代理),自然没有事务支持。
示例代码
java
// 错误:未加 @Service,未被 Spring 管理
public class UserService {
@Transactional
public void updateUser() {
userMapper.update();
}
}
// 调用时手动 new(无事务)
UserService userService = new UserService();
userService.updateUser();
解决方案
- 给类加
@Service注解,通过@Autowired注入使用; - 确保对象是从 Spring 容器中获取的(而非手动实例化)。
七、事务管理器配置错误 / 缺失
场景描述
- 未配置
PlatformTransactionManagerBean; - 多数据源时,事务管理器指定错误;
- 未开启事务支持(非 Spring Boot 项目)。
失效原因
Spring 事务需要 PlatformTransactionManager(如 DataSourceTransactionManager)来管理事务生命周期(开启 / 提交 / 回滚)。无配置或配置错误时,事务注解无法生效。
常见错误场景
- 非 Spring Boot 项目未加
@EnableTransactionManagement注解; - 多数据源时,未通过
@Transactional(transactionManager = "xxxTxManager")指定对应的事务管理器; - 数据源未注入事务管理器(如
DataSourceTransactionManager未传入dataSource)。
解决方案
- Spring Boot 项目:引入
spring-boot-starter-jdbc或spring-boot-starter-data-jpa,自动配置DataSourceTransactionManager; - 非 Spring Boot 项目:添加
@EnableTransactionManagement,并手动配置事务管理器:
less
@Configuration
@EnableTransactionManagement
public class TxConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
八、事务传播机制配置不当
场景描述
事务方法的传播机制配置为 不支持事务 的类型,如 NOT_SUPPORTED、NEVER。
失效原因
Spring 事务传播机制定义了方法间调用的事务行为,以下传播机制会导致事务失效:
Propagation.NOT_SUPPORTED:以非事务方式执行,若当前存在事务则挂起;Propagation.NEVER:以非事务方式执行,若当前存在事务则抛出异常;Propagation.SUPPORTS:若当前无事务,则以非事务方式执行(仅依赖外层事务)。
示例代码
java
@Service
public class UserService {
// 错误:传播机制为 NOT_SUPPORTED(不支持事务)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateUser() {
userMapper.update();
}
}
解决方案
使用支持事务的传播机制(默认 Propagation.REQUIRED 即可):
java
@Transactional(propagation = Propagation.REQUIRED) // 默认值,无需显式配置
public void updateUser() {
// ...
}
九、数据库本身不支持事务
场景描述
使用的数据库 / 表引擎不支持事务(如 MySQL 的 MyISAM 引擎)。
失效原因
Spring 事务是 基于数据库事务的上层封装,若底层数据库不支持事务,即使 Spring 配置正确,也无法实现事务特性。
常见情况
- MySQL 表引擎为 MyISAM(默认 InnoDB 支持事务);
- 部分内存数据库(如 H2 非事务模式)。
解决方案
将数据库表引擎改为支持事务的类型(如 MySQL 改为 InnoDB)。
十、多线程调用(事务上下文不共享)
场景描述
事务方法 A 中启动新线程,调用事务方法 B,方法 B 的事务与 A 独立,且 A 的异常不会导致 B 回滚。
失效原因
Spring 事务上下文(TransactionSynchronizationManager)是 线程局部变量(ThreadLocal) ,线程间不共享。新线程中调用的事务方法会创建独立的事务,与主线程事务无关。
示例代码
typescript
@Service
public class UserService {
@Transactional
public void updateUserA() {
userMapper.updateA();
// 新线程调用事务方法 B
new Thread(() -> updateUserB()).start();
int i = 1 / 0; // 主线程抛出异常,A 回滚,但 B 已提交
}
@Transactional
public void updateUserB() {
userMapper.updateB();
}
}
若需多线程事务一致性,需使用 分布式事务框架(如 Seata、Atomikos),或通过消息队列实现最终一致性。
总结:事务失效的核心排查思路
- 检查方法是否是
public,类 / 方法是否为final; - 检查是否是同一类内的内部调用(this 调用);
- 检查异常是否被捕获未抛出,或是否是未配置
rollbackFor的检查异常; - 检查 Bean 是否被 Spring 管理(是否有
@Service等注解,是否通过@Autowired注入); - 检查事务管理器是否配置正确(是否有
PlatformTransactionManagerBean,多数据源是否指定事务管理器); - 检查数据库 / 表是否支持事务(如 MySQL 引擎是否为 InnoDB)。