Spring 有哪几种事务传播行为?
- 标签:后端、Spring
🏛 业务架构师
事务传播行为定义"一个事务方法被另一个事务方法调用时,事务边界怎么定"。七种里我重点讲业务含义:
- REQUIRED(默认):有事务就加入,没有就新建。绝大多数业务动作的正确选择——订单创建和库存扣减同生共死;
- REQUIRES_NEW:挂起当前事务,开独立新事务。我用它的场景是审计日志:主业务回滚了,审计也必须留下"有人尝试过且失败了"的记录;还有消息通知,通知失败不能拖垮主流程。注意它会占第二个数据库连接,连接池要预留余量,否则高并发下死锁在连接池上;
- NESTED:保存点语义,部分回滚。适合批量处理中单条失败不影响整体——批量导入 100 条,第 37 条出错回滚到保存点继续;
- SUPPORTS / NOT_SUPPORTED / NEVER / MANDATORY:跟随/挂起/拒绝/强制,多为契约性声明,比如 MANDATORY 用来强制调用方必须先开事务,把隐性契约显性化。
业务上的两类高频事故:
- 同类内部自调用导致传播行为失效(没走代理),REQUIRED 变成"裸奔",记住:事务注解只对跨代理的调用生效;
- REQUIRES_NEW 的连接池耗尽——高峰期两个连接互相等待,池死锁。
我的规约:主子事务边界 = 业务边界,事务方法保持小而外显,嵌套事务必须在设计文档里写明理由。
🏗 系统架构师
事务传播行为回答"方法嵌套调用时,内层事务如何与外层事务相处",7 种我分三组记:
以当前事务为准:
- REQUIRED(默认):有事务加入,没有就新建;
- SUPPORTS:有就加入,没有就非事务跑;
- MANDATORY:必须有事务,否则抛异常。
新旧隔离:
- REQUIRES_NEW:挂起当前事务,开独立新事务,内层回滚不影响外层已挂起的事务,外层回滚也不影响已提交的内层——日志/审计必须留下痕迹的场景用它;
- NOT_SUPPORTED:挂起事务,以非事务方式执行;
- NEVER:有事务就抛异常。
嵌套:
- NESTED:通过**保存点(savepoint)**在当前事务内打标,内层回滚只回到保存点,外层可选择继续;外层回滚则内层一并回滚。
踩坑经验:REQUIRES_NEW 与 NESTED 的区别是两个独立事务 vs 同一事务的局部回滚;同类的自调用(this.method())不经过代理,注解全部失效,这是我排查"事务不生效"的第一嫌疑;NESTED 依赖 JDBC 保存点,JPA/某些数据源下受限,用前要验证。
💻 全栈开发专家
7 种,我重点讲项目里真正用到的四种:
- REQUIRED(默认):当前有事务就加入,没有就新建。绝大多数业务用它。
- REQUIRES_NEW:挂起当前事务,开一个独立新事务,内外互不影响(内回滚不影响外,外回滚不影响已提交的内)。场景:主流程失败也要记录操作日志。
- NESTED:嵌套事务(savepoint),内层回滚到保存点,外层可选择继续;外层回滚则内层必滚。场景:批量处理里单条失败不影响整批。
- SUPPORTS / NOT_SUPPORTED / MANDATORY / NEVER:有就用/没有就挂起/必须有/必须没有,做事务边界的防御性约束。
我踩过的两个坑(比背枚举有价值):
- 自调用失效:同类里
methodA()调this.methodB(),@Transactional(REQUIRES_NEW)的 B 不生效——绕过了代理对象。解法:拆 Bean 或注入自身代理。 - REQUIRES_NEW 耗尽连接池:外层事务占 1 个连接,内层新事务再借 1 个,并发一高连接池打满。我在压测时遇到过,最后限制并发 + 外层尽量无事务。
还有:传播行为是声明式事务(Spring)的概念,数据库本身只有事务和 savepoint;@Transactional 默认只回滚 RuntimeException 和 Error,检查异常要 rollbackFor。