直接回答:@Transactional 基于 AOP 代理实现:调用必须经过代理对象才会走事务拦截器。失效场景清单:同类内部自调用(this.method() 绕过代理,最常见);非 public 方法(CGLIB 代理限制);异常被 catch 吞掉没抛出(拦截器看不到异常自然不回滚);抛的是受检异常但 rollbackFor 没配(默认只对 RuntimeException/Error 回滚);方法被 final/static 修饰;多数据源下事务管理器配错;异步线程里调用(事务上下文是 ThreadLocal,跨线程失效)。
展开解析:自调用的解法四种:拆分到另一个 Bean(最干净,也符合职责分离);注入自身代理(@Lazy 自引用或 AopContext.currentProxy(),后者要开 exposeProxy 且代码侵入强);编程式事务 TransactionTemplate 兜底复杂场景;JDK 21 时代也可考虑 jOOQ/MyBatis 层的手工事务。传播级别的坑与失效并列常考:REQUIRES_NEW 挂起外层事务真开新连接(连接池要留余量,两个传播嵌套就占两个连接);NESTED 依赖数据库 savepoint(JPA 的某些实现不支持);同一个事务管理器管不了跨数据源的“分布式事务”,别指望传播级别跨库。诊断手段:开 org.springframework.transaction 的 DEBUG 日志看事务的创建与回滚记录;写集成测试断言“异常后数据不存在”,把事务行为纳入回归。还有个大坑:@Transactional 方法里发了消息/调了外部接口再回滚——外部副作用收不回,应该用事务同步器(afterCommit)或 outbox 模式延迟副作用。
追问方向:REQUIRES_NEW 与 NESTED 的实际差异?只读事务的优化机制是什么?
(约 500 字)