Spring 事务失效
失效场景汇总
| 场景 | 原因 |
|---|---|
| 方法非 public | AOP 代理限制 |
| 同类方法调用 | 不走代理 |
| 异常被捕获 | 无异常抛出 |
| 异常类型不匹配 | 默认只回滚 RuntimeException |
| 数据库不支持事务 | 如 MyISAM |
| 类未被 Spring 管理 | 无代理 |
| 方法是 final/static | 无法重写 |
详细分析
1. 方法非 public
@Service
public class UserService {
@Transactional
private void createUser() { // 失效
userDao.insert(new User());
}
@Transactional
protected void updateUser() { // 失效
userDao.update(new User());
}
}
// 原因:Spring AOP 基于代理,private/protected 方法无法被代理2. 同类方法调用
@Service
public class UserService {
public void methodA() {
methodB(); // 直接调用,不走代理
}
@Transactional
public void methodB() {
userDao.insert(new User());
}
}
// 解决方案一:注入自己
@Service
public class UserService {
@Autowired
private UserService self; // 注入自己
public void methodA() {
self.methodB(); // 走代理
}
@Transactional
public void methodB() {
userDao.insert(new User());
}
}
// 解决方案二:使用 AopContext
@Service
public class UserService {
public void methodA() {
((UserService) AopContext.currentProxy()).methodB();
}
@Transactional
public void methodB() {
userDao.insert(new User());
}
}
// 需要配置:@EnableAspectJAutoProxy(exposeProxy = true)3. 异常被捕获
@Service
public class UserService {
@Transactional
public void createUser() {
try {
userDao.insert(new User());
throw new RuntimeException("异常");
} catch (Exception e) {
// 异常被捕获,事务不会回滚
log.error("创建失败", e);
}
}
}
// 解决:手动标记回滚
@Service
public class UserService {
@Transactional
public void createUser() {
try {
userDao.insert(new User());
throw new RuntimeException("异常");
} catch (Exception e) {
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
log.error("创建失败", e);
}
}
}
// 或者重新抛出异常
@Service
public class UserService {
@Transactional
public void createUser() {
try {
userDao.insert(new User());
throw new RuntimeException("异常");
} catch (Exception e) {
log.error("创建失败", e);
throw e; // 重新抛出
}
}
}4. 异常类型不匹配
@Service
public class UserService {
@Transactional
public void createUser() throws Exception {
userDao.insert(new User());
throw new Exception("受检异常"); // 默认不会回滚
}
}
// 解决:指定回滚异常类型
@Service
public class UserService {
@Transactional(rollbackFor = Exception.class) // 指定所有异常都回滚
public void createUser() throws Exception {
userDao.insert(new User());
throw new Exception("受检异常");
}
}
// rollbackFor 默认值
@Transactional(rollbackFor = RuntimeException.class) // 默认5. 数据库不支持事务
-- MyISAM 不支持事务
CREATE TABLE user (
id BIGINT PRIMARY KEY,
name VARCHAR(100)
) ENGINE=MyISAM;
-- 使用 InnoDB
CREATE TABLE user (
id BIGINT PRIMARY KEY,
name VARCHAR(100)
) ENGINE=InnoDB;6. 类未被 Spring 管理
// 失效:没有 @Service/@Component 等注解
public class UserService {
@Transactional
public void createUser() {
userDao.insert(new User());
}
}
// 解决:添加注解
@Service
public class UserService {
@Transactional
public void createUser() {
userDao.insert(new User());
}
}7. 方法是 final/static
@Service
public class UserService {
@Transactional
public final void createUser() { // final,无法代理
userDao.insert(new User());
}
@Transactional
public static void deleteUser() { // static,无法代理
userDao.deleteById(1L);
}
}
// 原因:CGLIB 通过继承生成代理,final/static 方法无法重写8. 错误的传播行为
@Service
public class UserService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void createUser() {
// NOT_SUPPORTED:以非事务方式执行,事务失效
userDao.insert(new User());
}
@Transactional(propagation = Propagation.NEVER)
public void updateUser() {
// NEVER:不能在事务中执行,会抛异常
userDao.update(new User());
}
}9. 多线程调用
@Service
public class UserService {
@Transactional
public void createUser() {
new Thread(() -> {
userDao.insert(new User()); // 新线程,事务失效
}).start();
}
}
// 原因:事务信息存储在 ThreadLocal,新线程无法继承10. 事务管理器配置错误
@Configuration
public class DataSourceConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
// 如果有多个数据源,需要指定正确的事务管理器
@Transactional("secondaryTransactionManager")
public void createUser() {
// ...
}检查清单
□ 方法是否为 public?
□ 是否通过代理调用(非同类直接调用)?
□ 异常是否正确抛出?
□ 异常类型是否匹配(rollbackFor)?
□ 数据库是否支持事务(InnoDB)?
□ 类是否被 Spring 管理?
□ 方法是否为 final/static?
□ 传播行为是否正确?
□ 是否在多线程中执行?
□ 事务管理器是否正确配置?面试高频问题
Q1: Spring 事务什么时候会失效?
列出上述 10 种场景即可。
Q2: 为什么 private 方法事务失效?
Spring AOP 基于代理:
- JDK 动态代理:只能代理接口方法
- CGLIB:通过继承代理,private 方法无法被子类访问
Q3: 同类方法调用如何解决事务失效?
- 注入自己,通过代理调用
- 使用 AopContext.currentProxy()
- 拆分到不同类
Q4: 捕获异常后如何让事务回滚?
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();总结
事务失效核心要点:
1. 方法必须是 public
2. 同类调用需通过代理
3. 异常要正确抛出和匹配
4. 数据库要支持事务
5. 类要被 Spring 管理
6. 方法不能是 final/static