SpringBoot 事务失效报错全集|rollback不生效/事务不回滚/传播机制踩坑全解决

事务是后端数据一致性的核心保障,SpringBoot 基于 @Transactional 注解实现声明式事务,看似简单易用,但实际开发中,事务失效的坑却层出不穷------明明加了注解,异常发生后数据依旧提交、rollback 不生效,线上出现数据脏写、重复提交、数据不一致等严重问题,排查起来极其耗时。

✅ 事务不回滚:抛异常后数据仍提交,rollback 无效; ✅ 注解不生效:加了 @Transactional 完全没作用; ✅ 部分回滚:多表操作时,部分表回滚、部分表提交; ✅ 嵌套事务失效:子方法事务不生效,父方法回滚子方法不回滚; ✅ 异常被捕获:try-catch 后事务不回滚; ✅ 传播机制误用:REQUIRED/REQUIRES_NEW 用错导致事务混乱; ✅ 非public方法事务失效、自调用事务失效。

今天这篇,把 SpringBoot 事务失效的 10大高频场景+报错 一次性讲透,每个场景都配「报错现象+核心根因+可直接复制的解决方案+避坑技巧」,不管是新手还是老手,都能彻底搞懂事务失效的底层逻辑,避免线上踩坑,建议收藏,写业务代码时直接对照!

一、前置基础:事务生效的核心条件(先避80%的坑)

SpringBoot 声明式事务要生效,必须满足以下4个核心条件,少一个都会导致失效:

  1. 注解 @Transactional 必须加在 public 方法 上(非public方法不生效);

  2. 注解必须作用在 Spring 管理的Bean 上(无@Service/@Component等注解的类无效);

  3. 必须抛出 unchecked 异常(默认只回滚RuntimeException及其子类,checked异常不回滚);

  4. 事务的调用必须是 跨Bean调用(自调用/内部方法调用不生效)。

关键提醒:事务失效不会报明显的编译错误,只会出现"数据不回滚"的现象,这也是最容易被忽略、排查最耗时的点!

二、10大高频事务失效场景+解决方案(按出现概率排序)

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

1. 典型现象

方法内抛异常,用try-catch捕获后,异常未向上抛出,事务不回滚,数据正常提交。

2. 错误代码示例

复制代码
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    @Override
    public void addUserAndOrder(UserDTO userDTO) {
        try {
            // 新增用户
            userMapper.insert(userDTO);
            // 模拟异常(如订单插入失败)
            int i = 1 / 0;
            // 新增订单
            orderMapper.insert(new OrderDTO());
        } catch (Exception e) {
            // 捕获异常但未抛出,事务无法感知
            e.printStackTrace();
        }
    }
}

3. 核心原因

Spring 事务的回滚机制,是通过 捕获方法抛出的异常 来触发的。如果异常被try-catch捕获且未重新抛出,Spring 无法感知异常,就不会执行回滚操作。

4. 解决方案(两种任选)

复制代码
// 方案1:捕获后重新抛出异常
@Transactional
@Override
public void addUserAndOrder(UserDTO userDTO) {
    try {
        userMapper.insert(userDTO);
        int i = 1 / 0;
        orderMapper.insert(new OrderDTO());
    } catch (Exception e) {
        e.printStackTrace();
        // 重新抛出异常,让Spring感知
        throw new RuntimeException(e);
    }
}

// 方案2:指定rollbackFor,捕获后手动回滚
@Transactional(rollbackFor = Exception.class)
@Override
public void addUserAndOrder(UserDTO userDTO) {
    try {
        userMapper.insert(userDTO);
        int i = 1 / 0;
        orderMapper.insert(new OrderDTO());
    } catch (Exception e) {
        e.printStackTrace();
        // 手动触发回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

场景2:@Transactional 加在非public方法上,注解失效

1. 典型现象

方法加了@Transactional注解,但异常发生后,事务不回滚,注解完全没作用。

2. 错误代码示例

复制代码
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    // 错误:方法为private,事务注解失效
    @Transactional
    private void addUser(UserDTO userDTO) {
        userMapper.insert(userDTO);
        int i = 1 / 0; // 抛异常,但数据仍提交
    }

    // 外部调用私有方法
    @Override
    public void addUserPublic(UserDTO userDTO) {
        addUser(userDTO);
    }
}

3. 核心原因

Spring 事务的实现依赖 AOP 动态代理,而AOP无法代理非public方法(private/protected/default),导致@Transactional注解无法生效。

4. 解决方案

将方法改为public,且确保方法被Spring管理(类上有@Service/@Component注解):

复制代码
// 正确:改为public方法
@Transactional
@Override
public void addUser(UserDTO userDTO) {
    userMapper.insert(userDTO);
    int i = 1 / 0;
}

场景3:自调用/内部方法调用,事务失效

1. 典型现象

同一个Service类中,无事务的方法调用有事务的方法,事务不生效;或有事务的方法调用本类其他事务方法,事务不回滚。

2. 错误代码示例

复制代码
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private OrderMapper orderMapper;

    // 无事务方法
    @Override
    public void addUserAndOrder(UserDTO userDTO) {
        // 自调用:本类有事务的方法
        addUser(userDTO);
        addOrder(new OrderDTO());
    }

    // 有事务方法
    @Transactional
    public void addUser(UserDTO userDTO) {
        userMapper.insert(userDTO);
        int i = 1 / 0;
    }

    @Transactional
    public void addOrder(OrderDTO orderDTO) {
        orderMapper.insert(orderDTO);
    }
}

3. 核心原因

自调用时,调用的是 类本身的方法,而非Spring动态代理后的对象,AOP无法拦截,事务注解无法生效。

4. 解决方案(两种任选)

复制代码
// 方案1:自己注入自己(推荐,简单易用)
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private OrderMapper orderMapper;
    // 自己注入自己,调用代理对象的方法
    @Autowired
    private UserService userService;

    @Override
    public void addUserAndOrder(UserDTO userDTO) {
        // 调用代理对象的事务方法
        userService.addUser(userDTO);
        userService.addOrder(new OrderDTO());
    }

    @Transactional
    public void addUser(UserDTO userDTO) {
        userMapper.insert(userDTO);
        int i = 1 / 0;
    }

    @Transactional
    public void addOrder(OrderDTO orderDTO) {
        orderMapper.insert(orderDTO);
    }
}

// 方案2:通过ApplicationContext获取代理对象
@Service
public class UserServiceImpl implements UserService, ApplicationContextAware {

    private ApplicationContext applicationContext;
    @Autowired
    private UserMapper userMapper;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void addUserAndOrder(UserDTO userDTO) {
        // 获取代理对象
        UserService userService = applicationContext.getBean(UserService.class);
        userService.addUser(userDTO);
    }

    @Transactional
    public void addUser(UserDTO userDTO) {
        userMapper.insert(userDTO);
        int i = 1 / 0;
    }
}

场景4:未指定rollbackFor,checked异常不回滚

1. 典型报错/现象

方法抛出IOException、SQLException等checked异常(非RuntimeException),事务不回滚,数据正常提交。

2. 错误代码示例

复制代码
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    // 错误:未指定rollbackFor,checked异常不回滚
    @Transactional
    @Override
    public void addUser() throws IOException {
        userMapper.insert(new UserDTO());
        // 抛出checked异常(IOException)
        throw new IOException("文件读取失败");
    }
}

3. 核心原因

Spring 事务默认只回滚 RuntimeException 及其子类,对于checked异常(需要手动try-catch或throws声明),默认不回滚。

4. 解决方案

在@Transactional注解中指定rollbackFor,覆盖默认规则:

复制代码
// 方案1:指定具体异常
@Transactional(rollbackFor = IOException.class)

// 方案2:指定所有异常(推荐,简化配置)
@Transactional(rollbackFor = Exception.class)

// 正确代码
@Transactional(rollbackFor = Exception.class)
@Override
public void addUser() throws IOException {
    userMapper.insert(new UserDTO());
    throw new IOException("文件读取失败");
}

场景5:事务传播机制误用,导致事务不生效/部分回滚

1. 典型现象

嵌套事务中,父方法回滚,子方法不回滚;或子方法抛异常,父方法不回滚;多表操作部分生效、部分失效。

2. 错误代码示例

复制代码
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private OrderService orderService;

    // 父事务
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void addUserAndOrder(UserDTO userDTO) {
        userMapper.insert(userDTO);
        // 调用子事务(误用NOT_SUPPORTED,不支持事务)
        orderService.addOrder(new OrderDTO());
    }
}

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderMapper orderMapper;

    // 子事务:不支持事务,会挂起父事务
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    public void addOrder(OrderDTO orderDTO) {
        orderMapper.insert(orderDTO);
        int i = 1 / 0; // 抛异常,但不回滚
    }
}

3. 核心原因

事务传播机制配置错误,常用传播机制误用(如NOT_SUPPORTED、NEVER),导致子事务不参与父事务,或父事务无法感知子事务异常。

4. 解决方案(常用传播机制推荐)

  • REQUIRED(默认):如果有父事务,子事务加入父事务;没有则新建事务(最常用,适合绝大多数场景);

  • REQUIRES_NEW:无论有无父事务,都新建独立事务(子事务回滚不影响父事务,适合日志、消息通知等场景);

  • SUPPORTS:有父事务则加入,没有则无事务(不常用);

  • 禁止使用:NOT_SUPPORTED、NEVER(容易导致事务失效)。

    // 正确配置:父事务默认REQUIRED,子事务也用REQUIRED
    @Service
    public class UserServiceImpl implements UserService {
    @Transactional
    @Override
    public void addUserAndOrder(UserDTO userDTO) {
    userMapper.insert(userDTO);
    orderService.addOrder(new OrderDTO());
    }
    }

    @Service
    public class OrderServiceImpl implements OrderService {
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void addOrder(OrderDTO orderDTO) {
    orderMapper.insert(orderDTO);
    int i = 1 / 0; // 子事务抛异常,父事务一起回滚
    }
    }

场景6:数据库引擎不支持事务,事务失效

1. 典型现象

注解配置正确、异常正常抛出,但事务依旧不回滚,数据正常提交。

2. 核心原因

使用了不支持事务的数据库引擎,最常见的是 MySQL 的 MyISAM 引擎(MyISAM 不支持事务,InnoDB 才支持事务)。

3. 解决方案

  1. 查看数据库表引擎:show table status like '表名';

  2. 将表引擎改为 InnoDB:alter table 表名 engine=InnoDB;

  3. 新建表时指定引擎:create table 表名 (...) engine=InnoDB;

场景7:事务超时,导致事务自动回滚/失效

1. 典型报错

复制代码
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was reached

2. 核心原因

事务执行时间过长,超过了默认超时时间(默认30秒),Spring 会自动回滚事务;或手动配置的超时时间过短,导致事务未执行完就被回滚。

3. 解决方案

手动配置事务超时时间,根据业务场景调整:

复制代码
// 配置超时时间为60秒(单位:秒)
@Transactional(rollbackFor = Exception.class, timeout = 60)
@Override
public void addUserAndOrder(UserDTO userDTO) {
    // 执行耗时操作(如批量插入、远程调用)
    userMapper.insert(userDTO);
    orderService.addOrder(new OrderDTO());
}

场景8:只读事务配置错误,导致无法修改数据

1. 典型报错

复制代码
org.springframework.dao.TransientDataAccessResourceException: Could not update

2. 核心原因

@Transactional 注解配置了 readOnly = true(只读事务),但方法中执行了insert/update/delete操作,只读事务禁止修改数据,导致操作失败。

3. 解决方案

只读事务只用于查询方法,修改方法删除 readOnly 配置:

复制代码
// 错误:修改方法用了只读事务
@Transactional(readOnly = true)
@Override
public void addUser(UserDTO userDTO) {
    userMapper.insert(userDTO);
}

// 正确:修改方法不配置readOnly,查询方法配置
// 查询方法(只读)
@Transactional(readOnly = true)
@Override
public UserDTO getUserById(Long id) {
    return userMapper.selectById(id);
}

// 修改方法(非只读)
@Transactional(rollbackFor = Exception.class)
@Override
public void addUser(UserDTO userDTO) {
    userMapper.insert(userDTO);
}

场景9:多线程调用,事务不生效

1. 典型现象

主线程调用子线程执行数据库操作,子线程抛异常,主线程事务不回滚;或子线程事务不生效。

2. 错误代码示例

复制代码
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addUser() {
        // 主线程插入用户
        userMapper.insert(new UserDTO());
        // 子线程执行操作
        new Thread(() -> {
            // 子线程事务不生效,抛异常不回滚
            userMapper.insert(new UserDTO());
            int i = 1 / 0;
        }).start();
    }
}

3. 核心原因

Spring 事务是基于 ThreadLocal 实现的,线程之间的事务上下文不共享,子线程无法继承主线程的事务,导致子线程事务不生效。

4. 解决方案

避免多线程中执行数据库操作;若必须使用,需手动管理事务(或使用分布式事务):

复制代码
// 手动管理事务(简化示例)
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private TransactionDefinition transactionDefinition;

@Override
public void addUser() {
    // 主线程事务
    TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
    try {
        userMapper.insert(new UserDTO());
        // 子线程手动管理事务
        new Thread(() -> {
            TransactionStatus childStatus = transactionManager.getTransaction(transactionDefinition);
            try {
                userMapper.insert(new UserDTO());
                int i = 1 / 0;
                transactionManager.commit(childStatus);
            } catch (Exception e) {
                transactionManager.rollback(childStatus);
            }
        }).start();
        transactionManager.commit(status);
    } catch (Exception e) {
        transactionManager.rollback(status);
    }
}

场景10:@Transactional 注解加在接口上,失效

1. 典型现象

将@Transactional注解加在Service接口上,实现类未加注解,事务不生效。

2. 错误代码示例

复制代码
// 接口加注解,实现类不加
public interface UserService {
    @Transactional(rollbackFor = Exception.class)
    void addUser(UserDTO userDTO);
}

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    // 实现类未加注解,事务失效
    @Override
    public void addUser(UserDTO userDTO) {
        userMapper.insert(userDTO);
        int i = 1 / 0;
    }
}

3. 核心原因

Spring AOP 动态代理默认基于 实现类 代理,注解加在接口上,实现类未继承注解,导致AOP无法拦截,事务失效。

4. 解决方案

将@Transactional注解加在 实现类的方法上(推荐),或加在实现类上:

复制代码
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    // 正确:注解加在实现类方法上
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addUser(UserDTO userDTO) {
        userMapper.insert(userDTO);
        int i = 1 / 0;
    }
}

三、事务失效万能排查步骤(5步定位问题)

  1. 查注解位置:是否加在 public 方法、Spring 管理的Bean上;

  2. 查异常处理:是否有try-catch未抛出,是否指定rollbackFor;

  3. 查调用方式:是否是自调用、内部方法调用;

  4. 查传播机制:是否误用NOT_SUPPORTED、NEVER等不常用机制;

  5. 查数据库:表引擎是否为InnoDB,是否支持事务。

四、生产避坑指南(必记)

  • 所有事务方法必须加 rollbackFor = Exception.class,避免checked异常不回滚;

  • 事务注解优先加在 实现类方法 上,不要加在接口上;

  • 禁止自调用,必须跨Bean调用或注入自身调用代理对象;

  • 多线程中禁止执行数据库操作,若必须执行,手动管理事务;

  • 查询方法加 readOnly = true,修改方法禁止加;

  • 合理配置事务超时时间,避免因超时导致事务回滚;

  • 数据库表必须使用 InnoDB 引擎,确保支持事务。

五、总结

SpringBoot 事务失效,90% 都是 注解位置错误、异常未抛出、自调用、传播机制误用、数据库引擎不支持 这五类问题。记住核心原则:

  • 注解要加在 public 方法、Spring Bean 上;

  • 异常要让 Spring 感知(不捕获,或捕获后重新抛出);

  • 调用要跨Bean,避免自调用;

  • 传播机制优先用 REQUIRED、REQUIRES_NEW;

  • 数据库引擎必须是 InnoDB。

掌握本文内容,彻底解决事务不回滚、注解失效等问题,确保线上数据一致性,避免因事务失效导致的严重业务问题。

如果这篇文章帮到你了,记得点赞+收藏🌟!评论区说说你踩过的事务坑,一起交流避坑~

相关推荐
向上_503582912 小时前
配置Protobuf输出Java文件或kotlin文件
android·java·开发语言·kotlin
IAUTOMOBILE2 小时前
C++ 入门基础:开启编程新世界的大门
java·jvm·c++
秋野酱2 小时前
基于springboot的母婴商城系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
无籽西瓜a2 小时前
【西瓜带你学设计模式 | 第二期-观察者模式】观察者模式——推模型与拉模型实现、优缺点与适用场景
java·后端·观察者模式·设计模式
Counter-Strike大牛2 小时前
SpringBoot项目调用数据库函数报错Result consisted of more than one row
数据库·spring boot·后端
zihao_tom2 小时前
Springboot-配置文件中敏感信息的加密:三种加密保护方法比较
java·spring boot·后端
程序员buddha2 小时前
Java面试八股文框架篇
java·开发语言·面试
毕设源码-钟学长3 小时前
【开题答辩全过程】以 基于Java的医药进出口交易系统设计与实现为例,包含答辩的问题和答案
java·开发语言
Touch&3 小时前
Windows11多个JDK版本(Java8、Java11、Java17、Java21)下载安装和切换
java·jdk·jdk多个版本切换