★ @Transactional 在哪些场景下会失效?如何避免?

结论先行:@Transactional 依赖 Spring AOP 代理生效,凡是“没走代理、异常没抛出去、事务没接管”都会失效。高频场景可归纳为六类:同类自调用、方法非 public、异常被 catch 吞掉、只抛检查异常(默认不回滚)、事务入口/传播配错、跨线程调用。定位口诀:先问“有没有走代理?异常抛出去了吗?异常类型在不在回滚范围里?”

失效场景对照表

场景失效原因解决办法
同类 this 自调用调用发生在原始对象内部,绕过代理注入自身代理 / 拆 Bean / AopContext
方法非 publicAOP 代理默认只拦截 public 方法改为 public 并由外部走代理入口
catch 后异常没抛事务感知不到异常发生抛运行时异常或编程式回滚
只抛检查异常默认只回滚 RuntimeException/ErrorrollbackFor = Exception.class
事务入口/传播配错事务没在预期边界开启检查调用入口与 propagation
新线程/异步调用事务连接绑定当前线程事务不跨线程;另起事务或外层提交后处理

自调用失效示例

@Service
public class PayService {
    public void pay() {
        deduct(); // this.deduct() 未经过代理对象 → 事务注解不生效
    }

    @Transactional(rollbackFor = Exception.class)
    public void deduct() { /* 扣款逻辑 */ }
}

正确姿势:拆 Bean 走代理

@Service
public class PayFacade {
    @Autowired
    private PayService payService; // 注入另一个 Bean,拿到的是代理

    public void pay() {
        payService.deduct(); // 调用发生在代理上,事务生效
    }
}

异常处理三选一:不 catch 让它抛出去;catch 后 rethrow RuntimeException;或用 TransactionTemplate 手动控制提交回滚。

常见追问 / 记忆点

  • 追问:同类自调用怎么保事务?答:注入自身代理(@Lazy 自引用)、AopContext.currentProxy(),或干脆拆成两个 Bean。
  • 追问:catch 了业务异常但确实想回滚怎么办?答:catch 里 rethrow RuntimeException,或调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。
  • 记忆点:失效三查——“走代理了吗?异常抛了吗?异常在回滚白名单吗?”
笔记加载中…