Propagation本身不决定回滚,只控制事务创建与加入方式;回滚由异常类型、rollbackFor配置及是否进入代理链共同决定。

Propagation 本身不决定“是否回滚”,只决定“事务怎么建、怎么挂起、怎么加入”;真正触发回滚的是异常类型、rollbackFor 配置,以及调用是否真的进入了代理链。
为什么 @Transactional(propagation = Propagation.REQUIRES_NEW) 在同类私有方法里无效
Spring 的声明式事务基于 AOP 代理,只有通过 Spring 容器注入的 Bean 实例调用,才能被 TransactionInterceptor 拦截。直接在同一个类里用 this.saveLog() 或私有方法调用,压根没走代理,注解完全被忽略——此时 REQUIRES_NEW 就像没写一样。
- 现象:日志方法加了
@Transactional(propagation = Propagation.REQUIRES_NEW),但主事务一回滚,日志也丢了 - 根本原因:调用没出当前 Bean,没经过代理,事务上下文根本没切换
- 正确做法:把日志逻辑抽到独立的
LogService,用@Autowired注入后调用 - 替代方案(不推荐):改用
TransactionTemplate手动控制,但破坏了声明式事务的简洁性
REQUIRED 不等于“所有方法自动合并到一个事务”
Propagation.REQUIRED 是默认值,但它生效的前提是:调用必须跨 Bean 且走代理。很多人误以为只要两个方法都标了 @Transactional,嵌套调用就天然共享事务——其实不是。
- 场景:用户注册(
UserServiceImpl.create())里调用pointService.record(),两者都用REQUIRED - 结果:只有
pointService是另一个 Spring Bean,且被正确注入,才会加入同一事务 - 风险点:如果
pointService是 new 出来的、或用static方法调用,那它根本不在事务管理范围内 - 验证方式:在
record()方法里打印TransactionSynchronizationManager.isActualTransactionActive(),为false就说明没进事务
REQUIRES_NEW 和 NESTED 的关键区别在哪
REQUIRES_NEW 会挂起当前事务、开启全新物理事务,有自己的数据库连接和提交边界;而 NESTED 本质仍是单物理事务,靠数据库保存点(savepoint)实现局部回滚,不释放连接。
-
REQUIRES_NEW:适合日志、通知等“失败也不影响主流程”的操作;但高并发下可能耗尽连接池 -
NESTED:仅 MySQL/Oracle 等支持 savepoint 的数据库可用;PostgreSQL 默认不支持;H2 测试库需显式启用 - 注意:
NESTED下子操作回滚只回滚到 savepoint,外层仍可继续执行并最终提交——但它不能防止外层因其他异常整体回滚 - 别混淆:
NESTED不是“子事务”,没有独立的事务 ID,也不会出现在show processlist里
嵌套调用中回滚被吞掉的典型陷阱
内层事务方法抛出异常,如果外层没 catch,异常会穿透导致外层也回滚;但如果外层 try-catch 了,又没手动标记回滚,就可能出现“内层失败、外层却提交”的数据不一致。
- 错误写法:
logService.logAction(...)抛RuntimeException,外层只catch(Exception)并吃掉 - 后果:日志没记成,主事务却正常提交了
- 修复方式:要么在 catch 块里调用
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),要么让异常向上冒泡 - 更稳妥的做法:对非关键操作(如发短信、写日志),用
REQUIRES_NEW+try/catch吞异常,确保不影响主流程
最常被忽略的一点:事务传播行为只是“事务怎么建”,不是“异常怎么流”。哪怕传播配置全对,只要调用没过代理、异常被静默捕获、或 rollbackFor 没覆盖实际异常类型,回滚一致性就依然崩塌。










