Spring 事务传播机制:别再只会 @Transactional 了
Spring 事务传播机制:别再只会 @Transactional 了
@Transactional 大概是 Spring 里用得最多的注解之一,但很多人只知道"加了它就有事务",一旦遇到嵌套调用、异常回滚、自调用失效等问题就抓瞎。今天来系统梳理一下 Spring 事务传播机制的核心知识点。
传播行为是什么?
传播行为(Propagation)解决的是:当一个事务方法调用另一个事务方法时,事务应该如何传播。 Spring 定义了 7 种传播行为:
| 传播行为 | 行为描述 |
|---|---|
| REQUIRED(默认) | 有事务就加入,没有就新建 |
| SUPPORTS | 有事务就加入,没有就以非事务方式执行 |
| MANDATORY | 必须有事务,否则抛异常 |
| REQUIRES_NEW | 总是新建事务,挂起当前事务 |
| NOT_SUPPORTED | 以非事务方式执行,挂起当前事务 |
| NEVER | 以非事务方式执行,有事务则抛异常 |
| NESTED | 嵌套事务(基于 Savepoint),无事务时同 REQUIRED |
最常用的两个:REQUIRED vs REQUIRES_NEW
这是面试高频题,也是实际开发中最容易踩坑的地方。
REQUIRED(默认):内外方法共享同一个物理事务,同生共死。内层抛异常会导致整个事务回滚。
REQUIRES_NEW:内层方法会挂起外层事务,开启一个全新的独立事务。内外互不影响——内层回滚不影响外层提交,外层回滚也不影响已提交的内层。
举个实际场景:
@Service
public class OrderService {
@Autowired
private LogService logService;
@Transactional
public void createOrder(Order order) {
orderDao.insert(order);
try {
logService.saveLog("创建订单"); // 日志记录不应影响主流程
} catch (Exception e) {
log.error("日志记录失败", e);
}
// 即使日志失败,订单仍然提交
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String msg) {
logDao.insert(msg);
}
}
这里日志记录用 REQUIRES_NEW,即使主订单事务回滚,日志仍然保留(方便排查问题);即使日志写入失败,也不影响订单提交。
容易踩的坑
1. 自调用失效
@Service
public class UserService {
@Transactional
public void outer() {
this.inner(); // 直接调用,不走代理,事务不生效!
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
// ...
}
}
Spring 事务基于 AOP 代理实现,this 调用绕过了代理,传播行为完全不生效。 解决办法:注入自身代理、用 AopContext.currentProxy(),或者拆到另一个 Bean 里。
2. 异常被 catch 吞掉
@Transactional
public void doSomething() {
try {
innerService.update(); // 内层抛异常
} catch (Exception e) {
// 吞掉了,事务不会回滚
}
}
默认只有 RuntimeException 和 Error 才会触发回滚,受检异常需要显式指定 rollbackFor = Exception.class。
3. REQUIRES_NEW 的连接和锁风险
外层占一个连接,内层 REQUIRES_NEW 再占一个,高并发下连接池容易被打满。如果内外操作同一行数据,还可能引发死锁。
4. NESTED 不是 REQUIRES_NEW
NESTED 基于 JDBC Savepoint,是同一个事务内的"回滚点",不是独立事务。内层回滚只回到保存点,外层可以继续;但外层回滚会连带内层一起回滚。
传播行为 vs 隔离级别
这两个是正交的概念:
- 传播行为:解决事务"边界"问题(有没有事务、是新事务还是加入现有事务)。
- 隔离级别:解决并发事务之间的"可见性"问题(脏读、不可重复读、幻读)。
可以组合使用,但要注意:加入现有事务时,内层指定的隔离级别会被忽略,沿用外层事务的隔离级别。
一句话总结
- 默认用 REQUIRED,需要独立事务边界时用 REQUIRES_NEW。
- 自调用不走代理,传播行为会失效。
- 默认只对
RuntimeException回滚,受检异常要加rollbackFor。 REQUIRES_NEW会额外占用连接,高并发场景慎用。NESTED是保存点而非新事务,别和REQUIRES_NEW搞混。
- 点赞
- 收藏
- 关注作者
评论(0)