云计算、AI、云原生、大数据等一站式技术学习平台

网站首页 > 教程文章 正文

Spring事务失效的7大场景:源码级排查方案来了!

jxf315 2025-05-11 16:22:17 教程文章 4 ℃

凌晨2点,线上交易系统突然出现资金漏洞——数据不一致但事务竟未回滚。这是Spring开发者最不愿面对的噩梦。本文从源码层面剖析7个高频致命陷阱,直击事务失效的底层真相,每个案例均附带可落地的解决方案。建议收藏,关键时刻能救火!


一、非public方法使用@Transactional(字节码级陷阱)

Spring事务基于AOP动态代理实现,而CGLIB代理无法拦截private/protected方法。源码中
AbstractFallbackTransactionAttributeSource的
computeTransactionAttribute方法直接跳过非public方法:

// TransactionAttributeSource类关键判断
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    return null; // 直接返回不处理
}

解决方案:将事务方法改为public,或改用AspectJ编译时增强。


二、自调用导致代理失效(动态代理机制盲区)

当类内部方法A调用带@Transactional的方法B时,绕过代理对象直接调用,导致事务失效。源码可见JdkDynamicAopProxy通过反射调用时,外部调用才会触发拦截:

// JdkDynamicAopProxy.invoke()
if (method.getDeclaringClass() == Proxy.class) {
    // 自调用直接执行原始方法
    return method.invoke(proxy, args);
}

解决方案

  1. 通过ApplicationContext获取代理对象调用
  2. 使用AopContext.currentProxy()
  3. 重构代码结构

三、异常类型未正确抛出(Rollback规则陷阱)

默认仅对RuntimeException和Error回滚。若捕获异常未重新抛出,或抛出checked异常,事务将提交。源码中
RuleBasedTransactionAttribute的rollbackOn方法决定回滚逻辑:

// 默认回滚规则
public boolean rollbackOn(Throwable ex) {
    return (ex instanceof RuntimeException || ex instanceof Error);
}

解决方案

  1. 手动设置@Transactional(rollbackFor=Exception.class)
  2. 避免在catch块中"吞掉"异常

四、数据库引擎不支持事务(MyISAM致命陷阱)

使用MyISAM引擎的表完全不支持事务!查看源码DataSourceUtils准备连接时,会通过DatabaseMetaData获取事务支持状态:

// 判断数据库是否支持事务
if (databaseMetaData.supportsTransactions()) {
    // 开启事务逻辑
}

解决方案:将所有表引擎改为InnoDB,执行ALTER TABLE table_name ENGINE=InnoDB;


五、传播机制使用错误(嵌套事务黑洞)

PROPAGATION_SUPPORTS等机制在嵌套调用时可能失效。源码中
AbstractPlatformTransactionManager处理传播行为时,会根据存在状态决定是否创建新事务:

// 处理PROPAGATION_REQUIRES_NEW
if (isExistingTransaction(transaction)) {
    suspend(transaction); // 挂起当前事务
    try {
        // 创建新事务
    } finally {
        resume(transaction);
    }
}

典型错误场景

  • REQUIRED方法调用REQUIRES_NEW未通过代理
  • NESTED在MySQL中实际使用保存点

解决方案:用代码结构明确事务边界,慎用复杂传播机制。


六、多线程环境下事务分离(连接池地狱)

当使用@Async或线程池时,事务上下文无法跨线程传递。源码中
TransactionSynchronizationManager使用ThreadLocal存储资源:

private static final ThreadLocal<Map<Object, Object>> resources = 
    new NamedThreadLocal<>("Transactional resources");

解决方案

  1. 使用编程式事务管理
  2. 传递必要参数到子线程
  3. 使用TransactionTemplate

七、注解被错误覆盖(继承链危机)

当子类重写父类@Transactional方法时,注解可能失效。源码中
SpringTransactionAnnotationParser优先读取接口/类的注解:

// 注解查找顺序
methodAnnotation = AnnotationUtils.findAnnotation(method, Transactional.class);
classAnnotation = AnnotationUtils.findAnnotation(targetClass, Transactional.class);

解决方案

  1. 在子类方法显式添加注解
  2. 使用接口声明事务方法
  3. 设置@Transactional(proxyTargetClass=true)

终极排查工具包

  1. 开启debug日志:logging.level.org.springframework.jdbc=DEBUG
  2. 检查事务管理器是否注入:PlatformTransactionManager
  3. 使用TransactionTemplate手动测试
  4. 断点跟踪TransactionInterceptor.invoke()

记住:事务失效的本质都是代理未生效或资源未绑定。掌握这7个场景,您已具备源码级排障能力。转发本文到技术群,下次救火时您就是英雄!

Tags:

最近发表
标签列表