手记

Spring事务失效的十大陷阱解析:第七个坑曾让我背锅三天

以下是对原文内容的重写,保留原意不变,语言风格更凝练、逻辑更清晰,同时维持技术细节的准确性与表达的生动性:


一、凌晨三点的警报

“订单重复创建!用户投诉炸了!”
去年双11零点17分,手机疯狂震动。监控大屏上刺目的红色曲线中,我死死盯着日志里那行:
Transaction silently rolled back because it has been marked as rollback-only,冷汗浸透掌心。

经过72小时不眠不休的排查,真相令人扼腕:一个 @Async 注解与自调用组合,竟让事务在无声无息中失效
今天,我将自己踩过的坑、熬过的夜、总结出的排查清单,毫无保留地分享给你。


二、高频事务失效场景详解(附修复方案)

🔥 坑点一:方法修饰符不当

// ❌ 事务失效!Spring AOP仅支持 public 方法
@Transactional
protected void updateStock(Long skuId) { ... }

// ✅ 修复:改为 public,并避免 final/static 修饰
@Transactional
public void updateStock(Long skuId) { ... }

原理:JDK 动态代理只能拦截 public 方法;CGLIB 虽可代理非 public 方法,但 Spring 默认未启用,且存在兼容性隐患。


💣 坑点二:this 自调用绕过代理(高发事故!)

@Service
public class OrderService {
    @Transactional
    public void createOrder(Order order) {
        // ❌ this 调用跳过代理,事务失效
        this.deductStock(order);
    }

    @Transactional
    public void deductStock(Order order) { ... }
}

解决方案

  • 推荐:注入自身 Bean

    @Autowired private OrderService self;
    self.deductStock(order);
  • 备选:通过 AopContext.currentProxy()(需开启 @EnableAspectJAutoProxy(exposeProxy = true)
    ((OrderService) AopContext.currentProxy()).deductStock(order);

🌪️ 坑点三:异常被捕获导致“静默提交”

@Transactional
public void pay(Order order) {
    try {
        accountService.debit(order); // 可能抛出 BusinessException
    } catch (Exception e) {
        log.error("扣款异常", e); // ❌ 异常被吞,事务不回滚!
        throw new RuntimeException(e); // ✅ 必须重新抛出
    }
}

关键配置

// 显式指定回滚异常类型(默认仅对 RuntimeException/Error 回滚)
@Transactional(rollbackFor = Exception.class)

🌐 坑点四:多数据源下的事务管理器错配

// ❌ 未指定事务管理器,可能绑定到错误数据源
@Transactional
public void syncData() { ... }

// ✅ 显式指定事务管理器
@Transactional(transactionManager = "orderTransactionManager")

建议:启动时务必检查日志中的 Transaction manager 输出,确认绑定关系正确!


⚡ 坑点五:@Async 与事务的上下文冲突

// ❌ 异步方法在新线程执行,原事务上下文丢失
@Async
@Transactional
public void sendNotify(Order order) { ... } // 事务无效!

正确做法

  1. 主流程提交后再触发异步(如发布 ApplicationEvent
  2. 异步方法内独立开启新事务
    @Async
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendNotify(Order order) { ... }

📌 其他高频问题速查表

编号 问题现象 解决方案
坑6 MyISAM 表无法回滚 执行 SHOW TABLE STATUS,确保使用 InnoDB
坑7 传播行为误用(如 REQUIRES_NEW 嵌套) 绘制事务边界图,避免无谓嵌套
坑8 事务超时过短 设置 @Transactional(timeout = 30) 并优化慢 SQL
坑9 H2 测试环境事务行为不符 使用 Testcontainers 启动真实数据库
坑10 事务方法被 final/static 修饰 安装 IDEA 插件 “Spring Transaction Check” 进行编译期检查

三、生产级防御清单(已落地验证)
  1. 启动时自动扫描

    @Component
    public class TransactionChecker implements CommandLineRunner {
       @Override
       public void run(String... args) {
           // 扫描 @Transactional 方法,校验修饰符、异常配置等
           // 输出报告并阻断启动(关键系统必备)
       }
    }
  2. 事务日志埋点

    <!-- logback-spring.xml -->
    <logger name="org.springframework.transaction.interceptor" level="TRACE"/>

    关键观察Getting transaction for [...]Completing transaction for [...] 日志是否成对出现。

  3. 压测与验证
    • 模拟异常,验证数据一致性
    • 使用 Arthas 监控 @Transactional 方法耗时,识别慢事务

四、灵魂总结

事务不是魔法,而是一份契约

  • 它依赖代理机制,请敬畏 AOP 的边界
  • 它需要显式声明,拒绝“我以为它会回滚”
  • 它必须被验证,测试覆盖远胜文档承诺

这份经验,愿你少走弯路,不再深夜惊醒。

0人推荐
随时随地看视频
慕课网APP