一、前言
在使用 Spring Boot 开发时,很多人都会遇到一个经典问题:
为什么 @Transactional 明明加了,但事务就是不生效?
尤其是这种情况:
public void A() { B(); // B上有@Transactional }👉事务直接失效 ❌
这其实是 Spring 事务最核心、也是最容易踩坑的点。
二、先看一个真实踩坑案例
@Service public class UserServiceImpl implements UserService { @Override public void test() { doTran(); // 内部调用 } @Transactional public void doTran() { userMapper.insert("李四"); int i = 1 / 0; // 异常 userMapper.insert("王五"); } }你以为结果是:
李四 ❌ 回滚
王五 ❌ 不执行
实际结果是:
李四 ✅ 插入成功(没有回滚)
👉 ❗ 事务失效!
三、核心原因(本质)
🔥Spring 事务 = AOP + 代理模式
1️⃣ Spring 是怎么实现事务的?
当你写:
@Transactional public void doTran() {}Spring 并不是直接在这个方法上加事务,而是:
创建一个代理对象(Proxy)
2️⃣ 真正执行流程是:
外部调用 ↓ 代理对象(Proxy) ↓ 开启事务 目标方法 doTran() ↓ 异常 / 正常 ↓ 提交 or 回滚四、问题的关键:同类调用
错误调用路径
Controller ↓ test()【代理对象】 ↓ this.doTran() ❌ ↓ 原始对象执行(绕过代理) ↓ @Transactional 失效核心问题一句话:
❗同类内部调用(this.xxx)不会走代理对象
五、用一张图彻底理解
✅ 正常事务调用
Controller ↓ Proxy(代理) ↓ 开启事务 doTran() ↓ 提交 / 回滚❌ 同类调用
Controller ↓ Proxy(代理) ↓ test() ↓ this.doTran() ❌ ↓ 直接执行(无事务)六、为什么会这样?
因为:
AOP 是基于代理的
而:
this.xxx() =直接调用原始对象
👉 完全绕过代理
七、解决方案(3种)
✅ 方案1(推荐):事务加在外层方法
@Override @Transactional public void test() { doTran(); }调用链:
Controller → test(代理) → doTran(在事务中执行)
👉 ✔ 正常回滚
✅ 方案2:通过代理对象调用自己
@Autowired private UserService userService; public void test() { userService.doTran(); // 走代理 ✅ }解释:
@Autowired 注入的不是原始对象
@Autowired private UserService userService;👉 你以为拿到的是:UserServiceImpl 原始对象 ❌
👉 实际上拿到的是:UserServiceImpl 的代理对象(Proxy)✅
@Autowired 注入的是 Spring 创建的代理对象,而不是原始对象,所以通过它调用方法时会触发事务增强。
✅ 方案3(了解):AopContext
((UserService) AopContext.currentProxy()).doTran();👉 ❗ 不推荐(侵入性强)
八、常见事务失效场景(面试高频)
❌ 1. 同类调用
this.xxx(); // ❌
❌ 2. private 方法
private void doTran() {} // ❌ 无法代理
❌ 3. final 方法
public final void doTran() {} // ❌ 无法增强
❌ 4. 异常被吃掉
try {
int i = 1/0;
} catch (Exception e) {}
👉 ❗ 不会回滚
❌ 5. 抛 checked exception
throw new Exception(); // ❌ 默认不回滚
九、事务回滚规则(必须记住)
✔ RuntimeException → 回滚
✔ Error → 回滚
❌ Exception(受检异常)→ 默认不回滚
十、工程级最佳实践
✅ 标准写法
@Service public class UserService { @Transactional public void createUser() { insertUser(); insertProfile(); insertPointLog(); } }核心原则:
事务加在“业务编排方法”上
而不是:加在内部小方法上
十一、一句话总结(面试必备)
🔥 Spring 事务基于 AOP 代理实现,只有通过代理对象调用的方法,@Transactional 才会生效;同类内部调用会绕过代理,导致事务失效。
十二、口诀(强烈建议记住)
- 事务要生效,必须走代理
- 类内直接调,事务全失效
- 异常要抛出,回滚才生效
十三、延伸(进阶方向)
后续可以继续深入:
✔ 事务传播机制
✔ 事务 + Redis
✔ 事务 + MQ(最终一致性)
✔ 分布式事务
结尾
如果你踩过这个坑,说明你已经开始:
🔥 从“会用 Spring” → “理解 Spring 底层机制”
这一步非常关键。
下一篇:
《Spring 事务10大面试坑(含this调用、try-catch失效、线程问题)》