为什么 @Transactional 会失效? ⭐⭐⭐⭐⭐
面试频率:★★★★★
工作频率:★★★★★
⚡30 秒速记(复习只看这里)
🟢 一句话
Spring 事务本质依赖 Spring AOP Proxy,只有经过代理对象调用,事务才会生效。
🟡 五大高频原因(★★★★★)
- 同类内部调用(this.xxx())
- private 方法
- final 方法
- 异常被 catch
- rollbackFor 未配置
🟠 REQUIRES_NEW
- 挂起外层事务
- 开启独立事务
- 内外事务互不影响
- 常用于日志、审计、失败记录
🔴 一句话
事务本质是 AOP Proxy,不经过代理对象,事务不会生效。
📚 完整知识
一、事务底层原理
Spring 并不是因为 @Transactional 注解才有事务。
真正执行流程:
Controller
│
▼
Spring Proxy(代理对象)
│
▼
TransactionInterceptor
│
▼
PlatformTransactionManager
│
▼
开启事务
│
▼
执行目标方法
│
├── 成功 → Commit
└── 异常 → Rollback
真正负责事务的是:
TransactionInterceptor
不是注解。
二、为什么会失效?
① 同类内部调用(★★★★★)
错误
@Service
public class OrderService {
public void createOrder() {
saveOrder();
}
@Transactional
public void saveOrder() {
}
}
实际上执行的是:
this.saveOrder();
没有经过 Spring Proxy。
事务不会开启。
正确
@Service
public class OrderService {
@Autowired
private OrderTxService orderTxService;
public void createOrder() {
orderTxService.saveOrder();
}
}
② private 方法(★★★★★)
@Transactional
private void save() {
}
Spring 默认不会代理 private 方法。
事务失效。
③ final 方法(★★★★☆)
@Transactional
public final void save() {
}
CGLIB 需要重写方法。
final 无法重写。
事务失效。
④ 异常被 catch(★★★★★)
错误:
@Transactional
public void save() {
try {
int i = 1 / 0;
} catch (Exception e) {
log.error("error");
}
}
Spring认为:
方法正常结束。
最终:
Commit
正确:
catch (Exception e) {
throw e;
}
或者:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
⑤ rollbackFor 未配置(★★★★★)
默认只回滚:
RuntimeException
Error
例如:
throw new Exception();
不会回滚。
正确:
@Transactional(
rollbackFor = Exception.class
)
三、REQUIRES_NEW
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
表示:
挂起外层事务,开启新的独立事务。
例如:
外层事务
↓
保存订单
↓
调用 saveLog()
↓
REQUIRES_NEW
↓
日志提交成功
↓
恢复外层事务
↓
订单异常
↓
订单回滚
最终:
订单 ❌
日志 ✅
适用:
- 操作日志
- 审计日志
- 消息记录
- 失败记录
一般不要用于订单、库存等核心业务。
💼 工作场景
场景一:本地事务
保存订单
↓
扣库存
↓
更新积分
如果:
更新积分失败
最终:
订单回滚
库存回滚
积分回滚
属于:
本地事务
场景二:数据库 + MQ
保存订单
↓
扣库存
↓
发送 MQ
↓
更新积分
如果:
MQ 已发送成功
↓
更新积分失败
↓
数据库回滚
最终可能:
数据库没有订单
MQ 已消费
普通 @Transactional
无法保证:
数据库 + MQ 原子性
通常需要:
- 本地消息表
- RocketMQ 事务消息
- 最终一致性方案
场景三:微服务
订单服务
↓
库存服务
↓
积分服务
即使:
@Transactional
也只能保证:
订单数据库
不能保证:
库存服务
积分服务
一起回滚。
属于:
分布式事务
🎤 面试回答(30 秒)
Spring 的事务底层是通过 AOP 代理实现的,而不是 @Transactional 注解本身。只有方法经过 Spring 创建的代理对象调用时,事务才会生效。如果同类内部调用、private 方法、final 方法、异常被 catch 没有继续抛出,或者抛出的异常不是默认回滚类型,都可能导致事务失效。另外,REQUIRES_NEW 会开启一个新的独立事务,即使外层事务回滚,内层事务也不会回滚,因此一般用于日志、审计等需要独立提交的场景。
🎯 面试追问
Q1:为什么 this 调用会失效?
答:
没有经过 Spring Proxy。
没有执行 TransactionInterceptor。
Q2:为什么事务依赖代理?
答:
Spring 使用 AOP 增强目标方法。
真正开启事务的是:
TransactionInterceptor
Q3:为什么 final 不行?
答:
CGLIB 通过继承重写方法实现增强。
final 无法重写。
Q4:数据库 + MQ 为什么不能只靠 @Transactional?
答:
@Transactional 只能保证本地数据库事务。
MQ 属于另外一种资源。
需要事务消息、本地消息表等方案保证最终一致性。
⚠️ 工作踩坑
✅ this.xxx()
事务失效
✅ catch 后没有继续抛异常
事务提交
✅ checked Exception
没有 rollbackFor
事务提交
✅ 滥用 REQUIRES_NEW
部分数据提交
部分数据回滚
❓ 自测(复习时只看这里)
- 为什么 @Transactional 会失效?
- Spring 事务底层是谁实现的?
- 为什么 this.xxx() 不生效?
- rollbackFor 默认回滚哪些异常?
- REQUIRES_NEW 是什么?
- 数据库 + MQ 为什么属于一致性问题?
- 什么情况下属于分布式事务?