事务是后端数据一致性的核心保障,SpringBoot 基于 @Transactional 注解实现声明式事务,看似简单易用,但实际开发中,事务失效的坑却层出不穷------明明加了注解,异常发生后数据依旧提交、rollback 不生效,线上出现数据脏写、重复提交、数据不一致等严重问题,排查起来极其耗时。
✅ 事务不回滚:抛异常后数据仍提交,rollback 无效; ✅ 注解不生效:加了 @Transactional 完全没作用; ✅ 部分回滚:多表操作时,部分表回滚、部分表提交; ✅ 嵌套事务失效:子方法事务不生效,父方法回滚子方法不回滚; ✅ 异常被捕获:try-catch 后事务不回滚; ✅ 传播机制误用:REQUIRED/REQUIRES_NEW 用错导致事务混乱; ✅ 非public方法事务失效、自调用事务失效。
今天这篇,把 SpringBoot 事务失效的 10大高频场景+报错 一次性讲透,每个场景都配「报错现象+核心根因+可直接复制的解决方案+避坑技巧」,不管是新手还是老手,都能彻底搞懂事务失效的底层逻辑,避免线上踩坑,建议收藏,写业务代码时直接对照!
一、前置基础:事务生效的核心条件(先避80%的坑)
SpringBoot 声明式事务要生效,必须满足以下4个核心条件,少一个都会导致失效:
-
注解 @Transactional 必须加在 public 方法 上(非public方法不生效);
-
注解必须作用在 Spring 管理的Bean 上(无@Service/@Component等注解的类无效);
-
必须抛出 unchecked 异常(默认只回滚RuntimeException及其子类,checked异常不回滚);
-
事务的调用必须是 跨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. 解决方案
-
查看数据库表引擎:
show table status like '表名'; -
将表引擎改为 InnoDB:
alter table 表名 engine=InnoDB; -
新建表时指定引擎:
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步定位问题)
-
查注解位置:是否加在 public 方法、Spring 管理的Bean上;
-
查异常处理:是否有try-catch未抛出,是否指定rollbackFor;
-
查调用方式:是否是自调用、内部方法调用;
-
查传播机制:是否误用NOT_SUPPORTED、NEVER等不常用机制;
-
查数据库:表引擎是否为InnoDB,是否支持事务。
四、生产避坑指南(必记)
-
所有事务方法必须加
rollbackFor = Exception.class,避免checked异常不回滚; -
事务注解优先加在 实现类方法 上,不要加在接口上;
-
禁止自调用,必须跨Bean调用或注入自身调用代理对象;
-
多线程中禁止执行数据库操作,若必须执行,手动管理事务;
-
查询方法加
readOnly = true,修改方法禁止加; -
合理配置事务超时时间,避免因超时导致事务回滚;
-
数据库表必须使用 InnoDB 引擎,确保支持事务。
五、总结
SpringBoot 事务失效,90% 都是 注解位置错误、异常未抛出、自调用、传播机制误用、数据库引擎不支持 这五类问题。记住核心原则:
-
注解要加在 public 方法、Spring Bean 上;
-
异常要让 Spring 感知(不捕获,或捕获后重新抛出);
-
调用要跨Bean,避免自调用;
-
传播机制优先用 REQUIRED、REQUIRES_NEW;
-
数据库引擎必须是 InnoDB。
掌握本文内容,彻底解决事务不回滚、注解失效等问题,确保线上数据一致性,避免因事务失效导致的严重业务问题。
如果这篇文章帮到你了,记得点赞+收藏🌟!评论区说说你踩过的事务坑,一起交流避坑~