Spring 事务失效

以下是 4 种最常见的"事务失效"场景,请务必避开:


场景一:由于"同类内部调用"导致的失效(最坑!)

这是新手最容易犯的错。

现象 :你有一个类 UserService,里面有两个方法 A 和 B。

  • 方法 A:没有@Transactional

  • 方法 B:加了 @Transactional(要求回滚)。

  • A 调用了 B

代码示例(失效版):

java 复制代码
@Service
public class UserService {

    // 方法 A:没有事务
    public void methodA() {
        // ... 做一些事
        this.methodB(); // <--- 关键在这里!直接调用内部方法
    }

    // 方法 B:声明了事务
    @Transactional
    public void methodB() {
        userMapper.insert(new User());
        int i = 1 / 0; // 模拟报错,期望回滚
    }
}

结果 :当你调用 methodA 时,methodB 的事务不会生效,数据插入成功,不会回滚。

原因分析:

Spring 的事务是基于 代理模式 (Proxy Pattern) 实现的。

  1. 当你从外部 (比如 Controller)调用 UserService 时,你拿到的其实是 Spring 生成的代理对象

  2. 代理对象拿到请求,先开启事务,然后调用真正的目标对象。

  3. 但是,如果你在 methodA 内部直接写 this.methodB(),这里的 this 指的是目标对象本身,而不是代理对象。

  4. 这就相当于绕过了代理,直接执行了代码。既然没经过代理,事务切面自然就没机会执行。

怎么解决?

  1. 方案一(推荐):拆分文件 。把 methodB 移到另一个 Service 类中,然后注入进来调用。

  2. 方案二(自己注入自己)

    java 复制代码
    @Service
    public class UserService {
        @Autowired
        private UserService self; // 注入代理后的自己
    
        public void methodA() {
            self.methodB(); // 通过代理对象调用,事务生效
        }
    }

场景二:异常被"吃掉"了

这也是我们刚才讨论 AOP 顺序时提到的问题。

现象 :你自己写了 try-catch,把异常捕获了,而且没抛出去。

代码示例(失效版):

java 复制代码
@Transactional
public void updateUser() {
    try {
        userMapper.update(new User());
        int i = 1 / 0; // 报错
    } catch (Exception e) {
        e.printStackTrace(); 
        // 坑点:这里没有 throw e; 事务管理器以为一切正常
    }
}

原因 :Spring 事务管理器只有捕获到异常时,才会触发回滚。你自己把异常处理掉了,Spring 就会认为"操作成功",于是提交事务。

解决

  1. catch 块里手动 抛出:throw e;

  2. 或者手动触发生效:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();


场景三:异常类型不对(默认只认 RuntimeException)

现象:代码报错了,也抛出了异常,但数据库还是没回滚。

代码示例(失效版):

java 复制代码
// 假设这是一个 Checked Exception (编译时异常),比如 IO 异常
@Transactional 
public void readFile() throws IOException {
    userMapper.insert(new User());
    // 模拟文件读取失败
    throw new IOException("文件读取失败");
}

原因:

Spring 的 @Transactional 默认配置是:只有遇到 RuntimeException (运行时异常) 或 Error 时才回滚。

如果你抛出的是 Checked Exception(如 IOException, SQLException, 或者你自己定义的非运行时异常),Spring 默认是不回滚的。

解决(最稳妥的写法):

永远加上 rollbackFor 属性:

java 复制代码
@Transactional(rollbackFor = Exception.class) // 只要是异常,统统回滚
public void readFile() throws IOException { ... }

场景四:方法权限不是 public

现象 :你把 @Transactional 加在了一个 privateprotected 方法上。

原因:

Spring AOP 的底层实现(动态代理)通常要求代理的方法必须是 public 的。虽然较新的 Spring 版本(使用 CGLIB)可能在某些情况下支持,但官方建议和大多数情况下的铁律是:事务方法必须是 public。如果不是 public,Spring 会直接忽略这个注解,不报错,但也不生效。


总结一张表

失效场景 根本原因 解决方案
同类内部调用 绕过了 Spring 代理对象,使用了 this 注入自己 (self.method()) 或 拆分 Service
自己 try-catch 异常未抛出,AOP 捕获不到 throw e 或 手动设置 setRollbackOnly()
异常类型不对 默认只回滚 RuntimeException 使用 @Transactional(rollbackFor = Exception.class)
非 public 方法 AOP 代理限制 确保方法是 public
相关推荐
寻星探路5 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
想用offer打牌6 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
曹牧7 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
KYGALYX8 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法8 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
kfyty7258 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai
猫头虎9 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
李少兄9 小时前
在 IntelliJ IDEA 中修改 Git 远程仓库地址
java·git·intellij-idea
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端