实用指南:Spring事务什么时候会失效?
一、核心前提:Spring事务运行基础
Spring事务基于动态代理实现,仅当通过「Spring容器管理的Bean的代理对象」调用方法时,事务才会生效。所有失效场景的本质,都是破坏了“代理调用链路”或“事务上下文管理”。
二、11大事务失效场景
场景1:方法非public修饰(语法基础坑)
失效原理
Spring事务动态代理(JDK/CGLIB)会过滤非public方法:JDK代理仅能代理接口的public方法,CGLIB虽支持非public,但Spring源码(TransactionAttributeSource类)明确不解析非public方法的@Transactional注解。
失效代码
@Service
public class OrderService {
// private修饰,事务不生效
@Transactional
private void createOrder(Order order) {
orderMapper.insert(order);
throw new RuntimeException("异常"); // 数据仍插入,不回滚
}
}
解决方案
所有加@Transactional的方法,必须用public修饰。
场景2:方法用static/final修饰(阻断代理生成)
失效原理
- static方法:属于类级方法,不依赖对象实例,而动态代理基于“对象实例”生成,无法代理;
- final方法:CGLIB代理需生成原类子类并重写方法,final方法不可重写,无法植入事务逻辑。
失效代码
@Service
public class OrderService {
// static方法,事务失效
@Transactional
public static void deleteOrder(Long id) {
orderMapper.deleteById(id);
}
// final方法,事务失效
@Transactional
public final void refundOrder(Long id) {
orderMapper.updateStatus(id, 2);
}
}
解决方案
移除static/final关键字,确保方法为“public非静态非final”。
场景3:同类方法内部调用(最常见逻辑坑)
失效原理
同一类中,“非事务方法A”调用“事务方法B”时,调用的是「原对象的B方法」,而非「代理对象的B方法」,绕开代理导致事务逻辑未触发。
失效代码
@Service
public class OrderService {
// 非事务方法
public void submitOrder(Order order) {
this.createOrder(order); // 内部调用,没走代理
}
// 事务方法(被内部调用,失效)
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order);
throw new RuntimeException("异常"); // 数据已插入,不回滚
}
}
解决方案(按优先级)
- 拆类:将
createOrder放到新Service(如OrderTxService),注入后通过代理调用; - 自注入代理:本类注入自己的代理对象(
@Autowired private OrderService proxy),用proxy.createOrder()调用; - 暴露代理:启动类加
@EnableAspectJAutoProxy(exposeProxy = true),用(OrderService)AopContext.currentProxy().createOrder()调用。
场景4:未捕获异常(吞掉异常,事务无感知)
失效原理
Spring事务通过“捕获方法抛出的异常”判断是否回滚,若用try-catch捕获异常且未重新抛出,Spring认为“执行成功”,不触发回滚。
失效代码
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
try {
orderMapper.insert(order);
int i = 1 / 0; // 模拟异常
} catch (Exception e) {
e.printStackTrace(); // 只打印,不抛异常,事务失效
}
}
}
解决方案
捕获异常后重新抛出(如throw new RuntimeException(e)),或配合rollbackFor声明回滚异常。
场景5:异常类型不匹配(默认仅回滚运行时异常)
失效原理
Spring事务默认仅对「RuntimeException」和「Error」回滚,对「Checked异常(如IOException、SQLException)」不回滚,认为其是“可预期的手动处理异常”。
失效代码
@Service
public class OrderService {
// 抛Checked异常,默认不回滚
@Transactional
public void exportExcel() throws IOException {
orderMapper.exportData();
throw new IOException("导出失败"); // 数据已提交,不回滚
}
}
解决方案
通过rollbackFor显式声明回滚异常,推荐覆盖所有异常:@Transactional(rollbackFor = Exception.class)。
场景6:数据源未被Spring管理(事务管不到连接)
失效原理
Spring事务管理器依赖“Spring托管的数据源(DataSource)”,若数据源是手动new的(未加@Bean),事务管理器无法获取连接,注解失效。
失效代码
// 错误:数据源未交给Spring管理
public class DataSourceConfig {
public DataSource getDataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost/test");
return ds; // 无@Bean,Spring不管理
}
@Bean
public PlatformTransactionManager txManager() {
return new DataSourceTransactionManager(getDataSource()); // 依赖未托管数据源
}
}
解决方案
数据源加@Bean交给Spring管理,事务管理器注入Spring托管的数据源:
@Bean
public DataSource dataSource() { /* 配置数据源 */ }
@Bean
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
场景7:类未被Spring扫描成Bean(无代理对象)
失效原理
仅Spring Bean才会生成动态代理,若类没加@Service/@Component,或不在@ComponentScan范围内,@Transactional被忽略。
失效代码
// 无@Service,Spring不扫描,非Bean
public class OrderService {
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order);
}
}
解决方案
- 类加
@Service/@Component; - 若路径不符,启动类加
@ComponentScan(basePackages = "com.xxx.service")。
场景8:事务传播属性配置错误(主动放弃事务)
失效原理
传播属性定义多事务嵌套规则,配置“不支持事务”的属性会导致失效,常见错误属性:
- NOT_SUPPORTED:非事务方式运行,暂停当前事务;
- NEVER:必须无事务,有则抛异常;
- SUPPORTS:无外层事务则不启用事务。
失效代码
@Service
public class PayService {
// 传播属性设为NOT_SUPPORTED,无事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void pay(Long orderId) {
payMapper.insertRecord(orderId);
throw new RuntimeException("支付异常"); // 数据已插入,不回滚
}
}
解决方案
日常用默认REQUIRED(有则加入,无则新建),特殊场景用REQUIRES_NEW(新建事务)或NESTED(嵌套事务)。
场景9:数据库引擎不支持事务(底层不支持)
失效原理
Spring事务依赖数据库引擎支持,如MySQL的MyISAM是“非事务引擎”,InnoDB才支持事务。
失效代码
-- MyISAM引擎,无事务
CREATE TABLE `order` (
`id` bigint PRIMARY KEY AUTO_INCREMENT
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
解决方案
- 用
InnoDB引擎(MySQL 5.5+默认); - 已创建表修改引擎:
ALTER TABLEorderENGINE=InnoDB;。
场景10:事务超时(超时后回滚失效)
失效原理
timeout属性(默认-1,无超时)设为N秒时,方法执行超N秒,Spring强制终止事务并回滚,但长时间SQL可能已执行完,导致回滚失效。
失效代码
@Service
public class OrderService {
// 超时3秒,SQL执行5秒
@Transactional(timeout = 3)
public void batchCreate(List list) {
for (Order o : list) { // 循环插入耗时5秒
orderMapper.insert(o);
Thread.sleep(5);
}
}
}
解决方案
- 调大
timeout(不建议过大,避免占用连接); - 拆分任务(如分批插入,每批提交事务)。
场景11:新线程中ThreadLocal上下文丢失(跨线程事务失效)
失效原理
Spring事务将数据库连接绑定到当前线程的ThreadLocal中,新线程(如new Thread()、线程池)的ThreadLocal为空,无法获取父线程的连接,只能新建连接,脱离原事务管理。
失效代码
@Service
public class OrderService {
@Transactional(rollbackFor = Exception.class)
public void createOrderWithLog(Order order) {
// 父线程:事务内操作(正常)
orderMapper.insert(order);
// 新线程:ThreadLocal为空,用新连接,脱离事务
new Thread(() -> {
logMapper.insertLog("创建订单:" + order.getNo()); // 不回滚
}).start();
throw new RuntimeException("异常"); // 父线程回滚,但日志已插入
}
}
执行结果
父线程“订单插入”回滚,新线程“日志插入”成功,数据不一致。
解决方案(按优先级)
事务后异步(推荐):用
TransactionSynchronizationManager在事务提交后执行异步操作:@Transactional(rollbackFor = Exception.class) public void createOrderWithLog(Order order) { orderMapper.insert(order); String orderNo = order.getNo(); // 事务提交后执行日志 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { new Thread(() -> logMapper.insertLog("创建订单:" + orderNo)).start(); } }); }消息队列解耦:事务方法提交后发送消息,消费者异步处理日志(彻底避免跨线程问题);
手动传递上下文:不推荐,需手动绑定父线程连接到新线程ThreadLocal,易引发线程安全问题。
三、事务失效排查流程(四步定位法)
- 检查Bean有效性:类是否加
@Service,是否在扫描范围内; - 检查方法修饰符:是否为
public,有无static/final; - 检查调用与异常:是否同类内部调用,异常是否捕获未抛,
rollbackFor是否配置; - 检查底层支持:数据库引擎是否为InnoDB,数据源是否托管,传播属性是否正确,是否跨线程。
文章的技术细节就到这里啦~
作为专注 Java 后端的学习者,我会持续输出:
Java 八股核心原理深度解析:例如 JVM、并发编程、集合框架等面试高频考点。
实用高频小知识点笔记:帮你快速掌握面试常问的基础细节。
原理拆解与知识点总结:助你构建扎实的知识体系,备战面试。
觉得有用的话,欢迎关注我! 后续内容将帮助你梳理关键知识,避免错过备战面试的重要笔记~
专注 Java 后端,持续输出干货!
浙公网安备 33010602011771号