Java 事务失效的 8 种核心场景(Spring 事务)

Java 里我们说的事务,默认都是 Spring 声明式事务(@Transactional),它是基于 AOP 动态代理 实现的,这是事务失效的根本原因 ——没有走 Spring 代理对象,而是直接调用了原生对象方法

我把最常见、面试必问、开发必踩的坑整理好了,直接看场景 + 原因 + 解决方案


1. 方法不是 public(最基础的失效)

失效代码

java

运行

@Transactional
private void addUser() { // private/protected/default 都不行
    userMapper.insert();
}

原因

Spring 事务只对 public 方法生效,AOP 代理无法拦截非 public 方法。

解决

方法改成 public


2. 同一个类内部调用(90% 的人踩过坑)

失效代码

java

运行

@Service
public class UserService {

    public void outer() {
        // 直接调用同类方法 → 没走代理 → 事务失效!
        this.inner();
    }

    @Transactional
    public void inner() {
        userMapper.insert();
    }
}

原因

this.方法()原生对象调用,不是 Spring 代理对象调用,AOP 无法拦截。

解决

  1. 注入自身代理对象
  2. 把方法拆到不同类
  3. 使用 AopContext.currentProxy ()

3. 异常被 try-catch 吃掉了

失效代码

java

运行

@Transactional
public void add() {
    try {
        userMapper.insert();
        int i = 1 / 0; // 抛异常
    } catch (Exception e) {
        // 异常被捕获 → 事务不知道要回滚
    }
}

原因

Spring 事务只有感知到未捕获的异常才会回滚。

解决

不要吞异常,要么抛出去,要么手动回滚:

java

运行

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

4. 抛出的异常类型不对

失效代码

java

运行

@Transactional // 默认只回滚 RuntimeException 和 Error
public void add() throws Exception {
    userMapper.insert();
    throw new Exception(); // 受检异常 → 不回滚!
}

原因

@Transactional 默认只对 RuntimeException/Error 回滚。

解决

指定回滚异常:

java

运行

@Transactional(rollbackFor = Exception.class)

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

失效场景

MySQL MyISAM 引擎不支持事务,无论怎么写都无效。

解决

把表引擎改成 InnoDB


6. 没有被 Spring 管理(类没加 @Service/@Component)

失效代码

java

运行

// 没加 @Service → 不是 Spring  bean → 事务无效
public class UserService {
    @Transactional
    public void add() {}
}

原因

Spring 只管理自己容器里的 Bean。

解决

类上加 @Service/@Component


7. 多线程调用(子线程事务失效)

失效代码

java

运行

@Transactional
public void outer() {
    new Thread(() -> {
        // 子线程独立连接 → 事务不生效
        userMapper.insert();
    }).start();
}

原因

Spring 事务是线程绑定的,子线程拿不到主线程的连接。

解决

子线程方法单独加 @Transactional,或不用异步。


8. 传播机制配置错误

失效代码

java

运行

@Transactional(propagation = Propagation.NOT_SUPPORTED)

原因

NOT_SUPPORTED / NEVER 这些传播机制本身就不开启事务

解决

使用默认的 REQUIRED


一句话总结根本原因

Spring 事务 = 动态代理 + public 方法 + 异常抛出 + 代理调用只要破坏任意一条,事务就可能失效。


总结

  1. 最常见失效:同类内部调用(this. 方法)异常被 catch方法非 public
  2. 根本原因:没走 Spring 代理对象
  3. 通用避坑:方法 public、异常不吞、同类调用用代理、rollbackFor=Exception.class

Spring 事务避坑清单

一、事务生效前置条件(缺一必失效)

  1. 类必须被 Spring 管理:添加 @Service / @Component / @Repository
  2. @Transactional 的方法必须是 public
  3. 调用必须通过 Spring 代理对象,不能用 this 内部自调用
  4. 数据库表引擎为 InnoDB(MyISAM 不支持事务)
  5. 保证数据源、事务管理器已正常配置(SpringBoot 自动配置一般无需手动处理)

二、高频失效场景 + 标准正确写法

场景 1:同类内部调用(最高发)

❌ 错误(this 调用,不走代理,事务失效)

@Service
public class OrderService {
    public void createOrder() {
        this.saveOrder(); // 原生对象调用,AOP无法拦截
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveOrder(){
        // 业务CRUD
    }
}

✅ 方案 1:暴露自身代理(推荐)

// 开启代理暴露
@EnableAspectJAutoProxy(exposeProxy = true)
public void createOrder() {
    OrderService proxy = (OrderService) AopContext.currentProxy();
    proxy.saveOrder(); // 走代理,事务生效
}

✅ 方案 2:拆分到不同 Service(最稳妥)


场景 2:try-catch 捕获异常,事务不回滚

❌ 错误(吃掉异常,Spring 感知不到)

@Transactional(rollbackFor = Exception.class)
public void pay() {
    try {
        // 数据库操作
        int a = 1 / 0;
    } catch (Exception e) {
        log.error("异常",e);
    }
}

✅ 正确 1:捕获后手动回滚

@Transactional(rollbackFor = Exception.class)
public void pay() {
    try {
        // 数据库操作
        int a = 1 / 0;
    } catch (Exception e) {
        // 手动标记回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        log.error("异常已回滚",e);
    }
}

✅ 正确 2:不捕获异常,直接向上抛出


场景 3:受检异常不回滚(默认只回滚运行时异常)

❌ 错误(抛出 Exception/IOException 等,不会回滚)

@Transactional
public void biz() throws Exception {
    throw new Exception("业务失败");
}

✅ 标准通用写法(全局全覆盖)

@Transactional(rollbackFor = Exception.class)
public void biz() throws Exception {
    throw new Exception("业务失败");
}

场景 4:传播行为配置错误导致无事务

❌ 以下配置天然关闭事务 / 禁止事务

// 不使用事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
// 强制非事务,有事务直接报错
@Transactional(propagation = Propagation.NEVER)

✅ 常规业务统一使用默认

// propagation = Propagation.REQUIRED (默认)
@Transactional(rollbackFor = Exception.class)

场景 5:子线程异步,事务隔离失效

❌ 错误(主线程事务无法传递到子线程)

@Transactional(rollbackFor = Exception.class)
public void mainBiz(){
    new Thread(()->{
        // 独立事务/无事务,互不影响
        mapper.insert();
    }).start();
}

✅ 解决:子线程单独加事务或统一同步执行


场景 6:非 public 方法加事务

❌ 错误(private/protected/ 包级私有全部无效)

@Transactional(rollbackFor = Exception.class)
private void updateData(){
}

✅ 强制改为 public


三、生产项目事务注解【统一模板】

直接复制全局复用,杜绝 99% 事务问题

import org.springframework.transaction.annotation.Transactional;

/**
 * 业务层统一事务注解
 * 所有新增/修改/删除 方法必加
 */
@Transactional(rollbackFor = Exception.class)
public void businessMethod() {
    // 增删改业务代码
}

四、快速排查事务失效 5 步口诀

  1. 看方法是不是 public
  2. 看是不是 this. 内部调用
  3. 看异常有没有被 全量 catch 吞掉
  4. 看有没有加 rollbackFor = Exception.class
  5. 检查表引擎是否为 InnoDB

五、补充:手动事务(无注解硬编码)

特殊场景不用注解,手动控制提交 / 回滚

@Autowired
private PlatformTransactionManager transactionManager;

public void manualTx(){
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
        // 业务CRUD
        transactionManager.commit(status);
    } catch (Exception e) {
        transactionManager.rollback(status);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值