Java 外功精要(6)——Spring 事务及其传播机制(2025-2026 生产级理解版)
Spring 事务是 Java 后端工程师最常使用、也最容易踩坑的“外功”之一。
尤其是事务传播行为(Propagation),面试必考、生产必踩、代码审查必看。
下面用最清晰的结构 + 通俗比喻 + 真实场景,把 Spring 事务的核心讲透。
一、Spring 事务的核心本质(一句话)
Spring 事务 ≠ 数据库事务
Spring 事务是对数据库事务的一种抽象与管理,通过 AOP 动态代理在方法执行前后插入 begin/commit/rollback 逻辑。
最核心的两句话:
- Spring 事务的边界 = 被 @Transactional 标注的方法(或 xml 定义的事务方法)
- 事务的传播行为 = 当方法 A 调用方法 B 时,事务应该如何“接力”或“新建”
二、Spring 事务的 7 种传播行为(Propagation)——必背表
| 传播行为(Propagation) | 值(枚举) | 通俗比喻 | 当外层已有事务时 | 当外层无事务时 | 面试最常问场景 | 实际使用频率(2025-2026) |
|---|---|---|---|---|---|---|
| REQUIRED | REQUIRED(默认) | “有饭局就一起吃,没饭局就自己开一桌” | 加入外层事务 | 新建事务 | 99% 普通业务方法 | ★★★★★ |
| SUPPORTS | SUPPORTS | “有饭局就蹭一口,没饭局就不吃” | 加入外层事务(不开启新事务) | 不开启事务(以非事务方式执行) | 查询方法、日志记录 | ★★★☆☆ |
| MANDATORY | MANDATORY | “必须有饭局才能吃,没饭局就报错” | 加入外层事务 | 抛出异常 | 必须在已有事务中执行的子操作 | ★★☆☆☆ |
| REQUIRES_NEW | REQUIRES_NEW | “不管有没有饭局,我都要自己开一桌” | 挂起外层事务,新建独立事务 | 新建事务 | 独立记账、发红包、发站内信、记录操作日志 | ★★★★☆ |
| NOT_SUPPORTED | NOT_SUPPORTED | “有饭局也先别吃,等我吃完再说” | 挂起外层事务,以非事务方式执行 | 非事务执行 | 需要非事务执行的特殊操作(如导出报表) | ★★☆☆☆ |
| NEVER | NEVER | “坚决不许有饭局,吃就报错” | 抛出异常 | 非事务执行 | 极少用,强制要求不能在事务中执行 | ★☆☆☆☆ |
| NESTED | NESTED | “在别人饭局里再开一个小灶” | 如果外层有事务,则开启一个嵌套事务(保存点) 外层回滚 → 内层也回滚 | 新建事务 | 保存点场景(部分失败仍可提交部分结果) | ★★★☆☆ |
三、最常考、最常踩的 6 种传播行为场景(带代码示例)
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private UserService userService;
@Autowired
private LogService logService;
/**
* 场景1:默认 REQUIRED(最常见)
* 下单 → 扣库存 → 扣余额 → 记录日志
*/
@Transactional
public void createOrder() {
orderDao.insertOrder();
userService.deductBalance(); // 加入外层事务
logService.writeLog(); // 加入外层事务
}
}
@Service
public class UserService {
/**
* 场景2:REQUIRES_NEW(最常用于独立记录)
* 即使外层订单事务回滚,发站内信、写操作日志也要成功
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotification() {
// 发站内信、写操作日志
// 即使外层回滚,这里也会提交
}
}
@Service
public class LogService {
/**
* 场景3:NESTED(保存点)
* 外层事务中有一部分操作允许失败,但整体仍想提交
*/
@Transactional(propagation = Propagation.NESTED)
public void recordPartialLog() {
// 如果这里抛异常,只回滚本方法
// 外层事务仍可继续提交已执行部分
}
}
四、事务失效的 8 大经典场景(生产必踩,面试必考)
- 非 public 方法(最常见失效原因)
- @Transactional 只能标注 public 方法
- 内部调用绕过代理(最阴险)
@Transactional
public void outer() {
this.inner(); // this 调用,绕过代理 → 事务失效
}
public void inner() { ... }
解决:用 AopContext.currentProxy() 或注入自己
- 事务方法被 private / final / static 修饰
- 数据库不支持事务(MyISAM 引擎)
- 事务传播行为为 SUPPORTS / NOT_SUPPORTED / NEVER
- 未开启事务管理(@EnableTransactionManagement)
- 异常被捕获未抛出(默认只回滚 RuntimeException)
try {
// 业务代码
} catch (Exception e) {
// 吃了异常 → 事务不回滚
}
解决:手动 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
- 多数据源未正确配置事务管理器
五、面试/生产最常问的 5 个问题(建议背熟)
- REQUIRED 和 REQUIRES_NEW 的区别?什么场景用 REQUIRES_NEW?
- NESTED 和 REQUIRES_NEW 的区别?(保存点 vs 独立事务)
- Spring 事务如何实现?(AOP 动态代理 + TransactionInterceptor)
- 事务失效的常见原因有哪些?
- 同一个类中,@Transactional 方法调用另一个 @Transactional 方法,事务如何传播?
六、一句话总结
Spring 事务的核心就是:用 AOP 在方法前后插入 begin/commit/rollback 逻辑,而传播行为决定了“当方法嵌套调用时,事务应该如何接力或独立”。
生产口诀:
默认 REQUIRED,日志/通知用 REQUIRES_NEW,保存点用 NESTED,查询用 SUPPORTS。
如果你现在能:
- 说出 7 种传播行为的含义和典型场景
- 写出 3 种事务失效的代码示例
- 解释为什么内部调用 this.method() 会导致事务失效
那这篇内容你就已经掌握了 Spring 事务的 80% 精髓。
想继续深入哪一块?
比如:
- 多数据源分布式事务(Seata / XA)
- 声明式事务源码(TransactionInterceptor)
- 编程式事务(TransactionTemplate)
- @Transactional 所有属性详解(rollbackFor、isolation、timeout 等)
随时告诉我,我继续展开~