Spring 事务失效的 8 种常见场景总结

Spring 事务失效的 8 种常见场景总结

在 Spring 开发中,@Transactional 注解是管理事务的利器,但如果使用不当,事务往往会"静悄悄"地失效,导致数据不一致。以下是导致事务失效的核心场景及解决方案。


1. 访问权限问题 (Access Modifier)

Spring 事务基于 AOP 实现。默认情况下,Spring 只会拦截 public 方法。

  • 失效场景 :方法修饰符为 privateprotectedpackage-visible(默认)。

  • 原因:Spring 的 AOP 代理(无论是 JDK 动态代理还是 CGLIB)在生成代理类时,无法正确拦截非 public 方法。

  • 代码示例

    java 复制代码
    @Transactional // 失效
    private void updateOrder() {
        // ...
    }

2. 方法内部自调用 (Self-Invocation)

这是最容易被忽视的场景。当同一个类中的一个普通方法调用另一个带有 @Transactional 的方法时,事务会失效。

  • 失效场景methodA 调用 methodBmethodB 有事务注解,但 methodA 没有。

  • 原因 :Spring 事务是基于代理对象 (Proxy) 的。在类内部调用 this.methodB() 时,通过的是目标对象 (Target) 直接调用,绕过了 Spring 生成的代理对象,因此切面逻辑(事务开启/提交)不会执行。

  • 代码示例

    java 复制代码
    public void methodA() {
        this.methodB(); // 这里的 this 是目标对象,不是代理对象,事务失效
    }
    
    @Transactional
    public void methodB() {
        // ...
    }
  • 解决方案

    1. 注入自身(使用 @Autowired@Resource)。
    2. 使用 AopContext.currentProxy() 获取当前代理对象调用。

3. 异常被 "吃掉" (Swallowing Exceptions)

事务回滚依赖于异常抛出。如果开发者在代码中手动捕获了异常且没有再次抛出,Spring 事务管理器就无法感知到异常,从而认为是正常执行,触发提交而非回滚。

  • 失效场景 :业务代码使用了 try-catch 块,但在 catch 中只是打印日志,未抛出异常。

  • 代码示例

    java 复制代码
    @Transactional
    public void updateUser() {
        try {
            // 数据库操作
            userMapper.update(user);
            int i = 1 / 0; // 模拟异常
        } catch (Exception e) {
            e.printStackTrace(); // 异常被捕获,事务不会回滚
        }
    }
  • 解决方案 :在 catch 块中通过 throw new RuntimeException(e) 再次抛出,或使用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 手动回滚。

4. 异常类型不匹配 (Exception Type Mismatch)

默认情况下,Spring 事务只有在捕获到 RuntimeException (运行时异常) 或 Error 时才会回滚。

  • 失效场景 :抛出了 Checked Exception(受检异常,如 IOException, SQLException),但 @Transactional 未做配置。

  • 代码示例

    java 复制代码
    @Transactional // 默认只回滚 RuntimeException
    public void readFile() throws Exception {
        // 如果抛出的是 Checked Exception,事务不会回滚
        throw new Exception("文件读取错误"); 
    }
  • 解决方案 :显式指定回滚异常类型:@Transactional(rollbackFor = Exception.class)

5. 类未被 Spring 管理 (Not a Spring Bean)

如果一个类没有被 Spring 容器管理(例如通过 new 关键字手动创建的对象),Spring 的 AOP 机制自然无法介入。

  • 失效场景

    java 复制代码
    // Service 没有加 @Service 或 @Component 注解
    public class OrderService {
        @Transactional
        public void createOrder() { ... }
    }
    
    // 在其他地方直接 new 使用
    OrderService service = new OrderService();
    service.createOrder(); // 事务失效

6. 方法是 Final 或 Static

  • 失效场景 :方法被修饰为 finalstatic
  • 原因
    • Final :如果是 CGLIB 代理(基于继承),无法重写 final 方法,导致无法添加事务逻辑。
    • Static:静态方法属于类而非对象,AOP 代理无法拦截静态方法。

7. 数据库引擎不支持事务

这是底层基础建设的问题。

  • 失效场景 :MySQL 使用了 MyISAM 存储引擎。
  • 原因MyISAM 引擎本身不支持事务,即使 Spring 层配置正确,数据库层也无法回滚。
  • 解决方案 :将表的存储引擎修改为 InnoDB

8. 错误的事务传播行为 (Propagation)

配置了不支持事务的传播属性。

  • 失效场景

    • Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    • Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • 代码示例

    java 复制代码
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void doSomething() {
        // 这里不会有事务
    }

总结对照表

场景 核心原因 关键解决方案
非 Public 方法 AOP 无法代理 修改为 public
自调用 绕过代理对象直接调用 使用 AopContext 或注入自身
Try-Catch 吞异常 事务管理器感知不到异常 抛出异常或手动 setRollbackOnly
受检异常 (Checked) 默认只回滚 RuntimeException 配置 @Transactional(rollbackFor = Exception.class)
Final/Static 方法 代理类无法重写/拦截 移除 final/static 关键字
数据库引擎 引擎本身不支持 使用 InnoDB
相关推荐
SuperherRo18 小时前
JAVA攻防-Shiro专题&断点调试&有key利用链&URL&CC&CB&原生反序列化&加密逻辑
java·shiro·反序列化·有key·利用链·原生反序列化·加密逻辑
桦说编程18 小时前
简单方法实现子任务耗时统计
java·后端·监控
爱笑的眼睛1118 小时前
超越可视化:降维算法组件的深度解析与工程实践
java·人工智能·python·ai
M***Z21018 小时前
springboot中配置logback-spring.xml
spring boot·spring·logback
盖世英雄酱5813618 小时前
物品超领取损失1万事故复盘(一)
java·后端
CryptoRzz18 小时前
印度尼西亚(IDX)股票数据对接开发
java·后端·websocket·web3·区块链
你怎么知道我是队长19 小时前
C语言---文件读写
java·c语言·开发语言
wszy180920 小时前
外部链接跳转:从 App 打开浏览器的正确姿势
java·javascript·react native·react.js·harmonyos
期待のcode20 小时前
认识Java虚拟机
java·开发语言·jvm