目录
一、前言:事务真的那么“简单”?
二、Spring事务的本质:一场代理的游戏
三、事务失效的 8 个高频原因(带解决方案)
1️⃣ 自调用:自己调用自己,代理失效
2️⃣ 非 public 方法无法被代理增强
3️⃣ 异常被 try-catch 吃掉
4️⃣ 抛出的不是 RuntimeException
5️⃣ 多线程、异步任务中的事务无效
6️⃣ 数据库本身不支持事务
7️⃣ 错误使用事务传播属性
8️⃣ 多数据源环境中事务配置缺失
四、真实项目中的事务使用建议
五、事务调试建议:如何定位事务到底有没有生效?
六、结语:别让“假事务”毁了你的生产系统
留言互动 & 投票
Spring 的事务用起来很方便,但在实际开发中,你是否遇到过加了 @Transactional
却根本不生效?本篇文章将从源码、原理和实战角度出发,拆解事务失效的常见场景,并给出解决方案,帮助你彻底搞懂事务底层逻辑,真正用好它!
在很多人眼里,Spring 事务就是在方法上加个 @Transactional
。
但当你真的遇到以下情况时,是不是就一脸问号:
明明加了注解,数据还是回滚不了?
方法里面抛异常了,事务竟然没回滚?
多个 Service 方法调用,只部分提交?
别急,下面我们一个个分析。
要想理解事务为什么会失效,必须先搞懂一个根本问题:
Spring 的事务到底是怎么实现的?
答案是:通过 AOP(面向切面编程)+ 代理机制。
也就是说,Spring事务的生效,依赖的是代理对象对目标方法的“增强”处理。如果你绕开了代理,自然也就绕开了事务。
所以事务注解 @Transactional
只是**“标记”你要开启事务**,真正控制事务的是代理层逻辑。而你常犯的错,可能正是“绕过了代理”。
这是最常见也是最隐蔽的坑之一。
// 假设你在同一个类中:
public void outerMethod() {
this.innerTransactionalMethod(); // ❌ 无效
}
问题在于:this
调用的是原始对象,Spring 事务代理根本不会介入。
✅ 正确姿势:将 innerTransactionalMethod()
拆分到另一个 Bean 中,通过 Spring 容器注入调用,才能走代理。
Spring 的事务代理只会增强 public 方法。
如果你在 private、protected 或默认修饰方法上加 @Transactional
,是无效的!
✅ 建议:所有需要事务控制的方法请使用 public
修饰。
Spring 默认基于异常回滚事务。如果你 try-catch 住了异常,事务不会回滚。
try {
service.doSomething(); // 抛了异常
} catch (Exception e) {
log.error("出错了", e); // ❌ 没有 rethrow
}
✅ 正确做法:
catch 后继续向上抛出异常;
或者调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
手动标记回滚。
默认情况下,Spring 只对 RuntimeException
和 Error
类型的异常进行回滚。
如果你抛出的是 checked exception
(比如 IOException),事务不会回滚。
✅ 解决方法:加上 rollbackFor
属性。
@Transactional(rollbackFor = Exception.class)
你在线程池或 @Async
中开启了新线程,这个线程不会继承当前事务上下文。
✅ 建议:
不要在事务中执行异步任务;
如果必须这么做,请使用事务消息中间件或分布式事务方案(如 Seata)。
比如你用的是 MySQL 的 MyISAM 存储引擎,它就是不支持事务的,即使 Spring 配置得再好,也不会回滚。
✅ 建议:使用 InnoDB 引擎。
例如你用了 REQUIRES_NEW
,会开启一个新的事务,外层回滚它也不回滚;或者用了 NESTED
,但数据库不支持保存点。
✅ 建议:
了解每种传播行为的语义,特别是 REQUIRED
(默认)、REQUIRES_NEW
、NESTED
的区别;
能不用嵌套事务就别用。
如果你用了多数据源(如读写分离、主从库),而只配置了默认的事务管理器,那其他数据源的操作是不会有事务控制的。
✅ 解决方案:
为每个数据源配置独立的事务管理器;
使用 @Transactional(transactionManager = "xxx")
明确指定。
总结一下我在项目中积累的一些实践经验:
✅ 将事务注解尽量写在 Service 层,避免写在 Controller;
✅ 事务中的方法尽量短小,不要塞一堆逻辑;
✅ 不要在事务中做耗时操作(如发邮件、发 MQ);
✅ 配合日志打印或监控工具记录事务行为;
✅ 如果项目复杂,考虑引入事务管理模板类统一处理。
如果你怀疑某段代码的事务没生效,可以:
在日志中打印是否开启了事务(Spring 的事务管理器可设置日志级别为 DEBUG
);
打断点调试,看代理类是否被调用;
写一个事务中 insert + 故意抛异常的测试用例验证。
事务是数据一致性的守门员,一旦出现问题,轻则数据错乱,重则“删库跑路”。
通过这篇文章,相信你已经能够掌握事务的运行机制,并且避开那些“默认配置陷阱”。
最后送你一句话作为总结:
Spring事务的本质是一场代理控制的“假象”,你看到的
@Transactional
,背后其实是满满的技巧与坑。
你踩过哪些事务失效的坑?
欢迎在评论区留言讨论,或者在文末投票参与互动