Spring事务失效场景
Spring 事务失效是日常开发中非常常见的问题,核心原因是 Spring 声明式事务的实现依赖 AOP 动态代理,一旦代理机制被破坏或不满足事务触发条件,事务就会失效。下面我会逐一讲解最常见的失效场景、原因和解决方案,内容由浅入深,方便你理解和排查。
一、常见的事务失效场景及解决方案
1. 方法不是 public 修饰
原因:Spring 事务管理器(TransactionInterceptor)只会拦截 public 方法,这是因为 Spring AOP 基于 JDK 动态代理时,只能代理接口的 public 方法;即使是 CGLIB 代理,Spring 也做了限制(源码中会检查方法修饰符)。
示例(失效代码):
@Service
public class UserService {
@Transactional
private void addUser(String username) { // private 方法,事务失效
// 数据库操作
}
}
解决方案:将方法改为 public 修饰:
@Service
public class UserService {
@Transactional
public void addUser(String username) { // 改为 public
// 数据库操作
}
}
2. 同一个类中方法内部调用
原因:Spring 事务通过代理对象实现,若在同一个类中,普通方法调用加了 @Transactional 的方法,本质是直接调用目标对象的方法,而非代理对象,AOP 无法拦截,事务失效。
示例(失效代码):
@Service
public class UserService {
// 普通方法
public void saveUser(String username) {
// 内部调用事务方法,事务失效
addUser(username);
}
@Transactional
public void addUser(String username) {
// 数据库操作
}
}
解决方案:
- 方案1(推荐):将事务方法抽取到另一个 Service 类,通过依赖注入调用(走代理);
- 方案2:自注入代理对象,通过代理对象调用(注意循环依赖问题):
@Service
public class UserService {
// 自注入代理对象(Spring 4.3+ 支持,或通过 ApplicationContext 获取)
@Autowired
private UserService userService;
public void saveUser(String username) {
// 通过代理对象调用,事务生效
userService.addUser(username);
}
@Transactional
public void addUser(String username) {
// 数据库操作
}
}
3. 事务注解属性配置错误
原因:@Transactional 的属性配置不符合业务场景,导致事务不生效或回滚异常。
常见错误配置:
propagation = Propagation.NOT_SUPPORTED(不支持事务,会挂起当前事务);propagation = Propagation.SUPPORTS(仅当有事务时才生效,无事务则不使用事务);rollbackFor未配置,默认只回滚RuntimeException和Error,检查异常(如SQLException)不会触发回滚。
示例(失效代码):
@Service
public class UserService {
// 配置错误:NOT_SUPPORTED 不支持事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addUser(String username) {
// 数据库操作,事务失效
}
// 未配置 rollbackFor,检查异常不会回滚
@Transactional
public void updateUser(String username) throws SQLException {
// 抛 SQLException,事务不回滚
throw new SQLException("更新失败");
}
}
解决方案:
@Service
public class UserService {
// 使用默认的 REQUIRED(支持当前事务,无则新建)
@Transactional
public void addUser(String username) {
// 事务生效
}
// 配置 rollbackFor 包含检查异常
@Transactional(rollbackFor = Exception.class)
public void updateUser(String username) throws SQLException {
throw new SQLException("更新失败"); // 事务回滚
}
}
4. 异常被捕获且未抛出
原因:Spring 事务需要捕获到方法抛出的异常才会触发回滚,如果异常被 try-catch 捕获且未重新抛出,事务管理器无法感知异常,不会回滚。
示例(失效代码):
@Service
public class UserService {
@Transactional
public void addUser(String username) {
try {
// 数据库操作
int a = 1 / 0; // 抛运行时异常
} catch (Exception e) {
// 捕获异常但未抛出,事务不回滚
e.printStackTrace();
}
}
}
解决方案:捕获异常后重新抛出,或手动触发回滚:
@Service
public class UserService {
@Transactional
public void addUser(String username) {
try {
int a = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
// 方案1:重新抛出异常
throw new RuntimeException(e);
// 方案2:手动回滚(适用于不想抛异常的场景)
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
5. 目标对象未被 Spring 容器管理
原因:Spring 事务仅对容器中的 Bean 生效,如果通过 new 关键字手动创建对象(而非依赖注入),该对象不是 Spring 代理对象,事务注解无效。
示例(失效代码):
@Controller
public class UserController {
@GetMapping("/add")
public void add() {
// 手动 new 对象,不是 Spring Bean,事务失效
UserService userService = new UserService();
userService.addUser("test");
}
}
@Service
public class UserService {
@Transactional
public void addUser(String username) {
// 数据库操作
}
}
解决方案:通过 @Autowired/@Resource 等方式注入 Spring 容器中的 Bean:
@Controller
public class UserController {
// 注入 Spring 管理的 UserService Bean
@Autowired
private UserService userService;
@GetMapping("/add")
public void add() {
userService.addUser("test"); // 事务生效
}
}
6. 数据库存储引擎不支持事务
原因:Spring 事务的底层依赖数据库事务,如果数据库存储引擎不支持事务(如 MySQL 的 MyISAM),即使 Spring 配置正确,事务也无法生效。
解决方案:将 MySQL 表的存储引擎改为 InnoDB(默认支持事务):
-- 修改表引擎
ALTER TABLE user ENGINE = InnoDB;
-- 创建表时指定引擎
CREATE TABLE user (
id INT PRIMARY KEY,
username VARCHAR(20)
) ENGINE = InnoDB;
7. 多线程调用
原因:Spring 事务是基于线程绑定的,子线程无法继承父线程的事务上下文,多线程执行数据库操作时,子线程的操作不在当前事务中,事务失效。
示例(失效代码):
@Service
public class UserService {
@Transactional
public void addUser(String username) {
// 主线程操作
insertUser(username);
// 子线程操作,事务失效
new Thread(() -> updateUser(username)).start();
}
}
解决方案:避免在事务方法中开启多线程执行数据库操作;若必须使用,需手动管理子线程的事务(如在子线程方法上单独加 @Transactional)。
二、如何快速排查事务失效?
- 检查方法修饰符:是否为
public; - 检查调用方式:是否为内部调用(非代理调用);
- 检查异常处理:是否捕获异常未抛出,或
rollbackFor配置错误; - 检查 Bean 管理:对象是否由 Spring 容器创建;
- 开启日志调试:添加日志配置,查看事务是否被拦截:
<!-- logback 配置示例 -->
<logger name="org.springframework.transaction" level="DEBUG"/>
<logger name="org.springframework.aop" level="DEBUG"/>
总结
- Spring 事务依赖 AOP 动态代理,任何破坏代理机制的操作(如内部调用、非 public 方法、手动 new 对象)都会导致事务失效;
- 事务生效的核心条件:
public方法 + 代理调用 + 异常抛出(或手动回滚) + 正确的注解配置 + 数据库支持事务; - 排查优先级:先检查调用方式和异常处理,再检查注解配置和 Bean 管理,最后确认数据库层面。

浙公网安备 33010602011771号