Spring 事务失效的 8 种场景,很多老手依然频繁踩雷
Spring 事务,是面试高频,也是线上事故的"重灾区"。
很多同学觉得:
加个
@Transactional就万事大吉了。
但在真实项目中,事务不回滚、部分回滚、甚至根本没开启,每天都在发生。
本文盘点 Spring 事务失效的 8 大经典场景,从原理 → 反例 → 正解 → 源码级原因,一次性讲透。
先给结论(速记版)
Spring 事务的本质是 AOP + 代理
只要代理没生效,或异常被"吃掉",事务一定会翻车
一、方法不是 public:最基础却最高频
❌ 错误示例
typescript
@Transactional
protected void createOrder() {
orderMapper.insert(order);
throw new RuntimeException();
}
✅ 原因
Spring 的事务 AOP 默认使用 JDK 动态代理 / CGLIB:
- JDK 动态代理 → 只能代理接口方法
- CGLIB → 只能拦截
public方法
@Transactional 注解在 protected / private / package 方法上:
不会被代理,事务直接失效
✅ 正确写法
typescript
@Transactional
public void createOrder() {
...
}
📌 经验法则:
事务方法一律写成
public
二、自调用(this 调用):老手最容易栽的坑
❌ 错误示例
typescript
@Service
public class OrderService {
public void create() {
save();
}
@Transactional
public void save() {
orderMapper.insert(order);
throw new RuntimeException();
}
}
✅ 原因
create() 内部调用 save():
kotlin
this.save(); // 调用的是目标对象本身
而 Spring 事务是通过 代理对象 实现的:
kotlin
外部调用 → 代理 → 目标方法
this调用 → 目标方法(绕过代理)
👉 事务完全失效
✅ 解决方案
方案 1:拆分 Service(推荐)
less
@Service
public class OrderTxService {
@Transactional
public void save() { ... }
}
@Service
public class OrderService {
@Autowired
private OrderTxService txService;
public void create() {
txService.save();
}
}
方案 2:注入自己(不优雅但可用)
ruby
@Autowired
private OrderService self;
self.save();
方案 3:AspectJ 模式(高级)
ini
spring.aop.proxy-target-class=true
三、异常类型不对:RuntimeException 与 Exception 的坑
❌ 错误示例
java
@Transactional
public void create() throws Exception {
orderMapper.insert(order);
throw new Exception();
}
✅ 原因
@Transactional 默认规则:
| 异常类型 | 是否回滚 |
|---|---|
| RuntimeException & Error | ✅ |
| Checked Exception | ❌ |
Exception 属于 受检异常,默认不回滚。
✅ 正确写法
java
@Transactional(rollbackFor = Exception.class)
public void create() throws Exception {
...
}
📌 强烈建议:
永远显式声明
rollbackFor
python
@Transactional(rollbackFor = Exception.class)
四、异常被吞掉:最隐蔽的"假成功"
❌ 错误示例
csharp
@Transactional(rollbackFor = Exception.class)
public void create() {
try {
orderMapper.insert(order);
int i = 1 / 0;
} catch (Exception e) {
log.error("error", e);
// ⚠️ 吞掉异常
}
}
✅ 原因
Spring 事务回滚的前提是:
异常抛出到代理层
你在方法内部把异常吃了,代理完全不知道发生了异常。
✅ 正确写法
php
catch (Exception e) {
log.error("error", e);
throw e; // 或 new RuntimeException(e)
}
📌 记住一句话:
事务回滚靠"抛异常",不是靠"打日志"
五、传播行为配置错误:REQUIRES_NEW 的误用
❌ 错误示例
java
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log() {
logMapper.insert(log);
}
外层事务回滚,日志却提交了。
✅ 原因
REQUIRES_NEW 的含义:
挂起外层事务,新建一个独立事务
- 内外事务完全隔离
- 外层回滚不影响内层
✅ 正确使用场景
✅ 审计日志
✅ 消息发送
✅ 不希望被主事务影响的操作
❌ 错误使用场景
❌ 核心业务数据
❌ 需要一起回滚的操作
📌 90% 的业务场景只需要:
ini
@Transactional(propagation = Propagation.REQUIRED)
六、数据库引擎不支持事务
❌ 典型场景
MySQL 使用 MyISAM
ini
CREATE TABLE order (
...
) ENGINE=MyISAM;
✅ 原因
- MyISAM:不支持事务
- InnoDB:支持事务
Spring 再怎么努力也没用。
✅ 检查方式
ini
SHOW TABLE STATUS WHERE Name = 'order';
✅ 正确做法
ini
ENGINE=InnoDB;
📌 现在还有人用 MyISAM,基本可以判定为事故级问题
七、非事务方法调用事务方法 + 多线程
❌ 错误示例
scss
@Transactional
public void create() {
new Thread(() -> {
orderMapper.insert(order);
}).start();
}
✅ 原因
Spring 事务是基于 ThreadLocal 绑定的:
一个线程 = 一个数据库连接 = 一个事务
新线程:
- 没有事务上下文
- 没有数据库连接绑定
👉 子线程操作不在事务中
✅ 正确做法
- 不要在事务方法中开线程
- 使用异步 + 新事务
less
@Async
@Transactional
public void asyncCreate() { ... }
八、没有被 Spring 管理:忘了 @Service / @Component
❌ 错误示例
typescript
// @Service 忘了写
public class OrderService {
@Transactional
public void create() {
orderMapper.insert(order);
}
}
✅ 原因
Spring 只对 Bean 创建代理。
不是 Bean → 没有代理 → 没有事务。
✅ 正确写法
kotlin
@Service
public class OrderService { ... }
📌 排查技巧:
- 看启动日志有没有 Bean 注册
- Debug 看
this是不是代理对象
九、Spring 事务失效全景对照表(建议收藏)
| 场景 | 是否失效 | 原因 |
|---|---|---|
| 非 public 方法 | ✅ | AOP 限制 |
| this 自调用 | ✅ | 绕过代理 |
| 吞掉异常 | ✅ | 未抛到代理 |
| 非 RuntimeException | ✅ | rollbackFor |
| REQUIRES_NEW 误用 | ⚠️ | 设计问题 |
| MyISAM | ✅ | 引擎不支持 |
| 多线程 | ✅ | ThreadLocal |
| 非 Spring Bean | ✅ | 无代理 |
十、如何快速定位事务问题?
✅ 1️⃣ 打印代理对象
less
log.info("class={}", AopContext.currentProxy().getClass());
✅ 2️⃣ 开启事务日志
ini
logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
✅ 3️⃣ Debug 断点
TransactionInterceptorDataSourceTransactionManager
十一、一句话总结
Spring 事务不是魔法,而是代理。
- 代理没生效 → 事务失效
- 异常没抛出 → 不会回滚
- 多线程 / 自调用 → 上下文丢失
真正理解这三点,事务问题至少少踩 80% 的坑。