Spring Boot 事务,@Transactional、代理、this 调用到底在搞什么?
一文吃透 Spring Boot 事务:
@Transactional、代理、this 调用到底在搞什么?
如果你用 Spring Boot 写过业务代码,那你一定写过
@Transactional。
但你很可能也遇到过这些诡异现象:
- 方法明明加了
@Transactional,却不回滚- 同一个类里调用方法,事务失效
- try-catch 一加,事务就“没了”
这些问题的根源,其实只有四个字:
代理机制。
这篇文章,我们就把 Spring Boot 事务 + AOP + 代理 一次性讲透。
一、先别急着看原理,先用一句大白话定个性
Spring 的事务不是写在方法里的,
而是 Spring 用“代理对象”在方法外面偷偷包了一层。
你写的代码里 根本没有事务代码,
但运行时却好像真的有:
@Transactional
public void saveOrder() {
// 业务逻辑
}
那事务到底在哪?
👉 在代理里,不在你这个类里。
二、@Transactional 到底是个什么东西?
先打破一个误解。
❌ 误解
@Transactional= 开事务
✅ 真相
@Transactional只是一个 标记
它真正的意思是:
“Spring 啊,这个方法你帮我用事务包一下。”
至于:
- 什么时候开事务
- 什么时候提交
- 出异常怎么回滚
👉 全是 Spring 在背后干的
三、事务是怎么“悄悄生效”的?
核心答案只有一个:
Spring 给你的 Service 创建了一个“代理对象”
一个你必须接受的事实
只要你用了事务,Spring 里就一定有 两个对象:
1️⃣ 目标对象(target):你写的 Service
2️⃣ 代理对象(proxy):Spring AOP 生成的
而且:
Spring 容器对外只暴露 proxy
你以为你在调用 OrderService,
实际上你调用的是:
OrderServiceProxy
四、代理对象到底干了什么?(人话版)
假设你写了这样一个方法:
@Transactional
public void saveOrder() {
// 业务逻辑
}
运行时,代理对象里真正执行的是类似这样的逻辑:
public void saveOrder() {
开启事务
try {
调用真正的 saveOrder()
提交事务
} catch (Exception e) {
回滚事务
throw e;
}
}
注意一句很关键的话:
事务代码不在你的方法里,在代理里。
五、那是不是所有 Bean 都会被代理?
不是,这点非常重要。
结论先行
只有“需要 AOP 增强的 Bean”才会被代理
普通 Bean 根本不会被代理
Spring 创建 Bean 的真实流程(简化版)
1️⃣ 扫描到一个类
2️⃣ new 出原始对象(target)
3️⃣ 判断:需不需要 AOP?
- 需要 → 创建 proxy,放进容器
- 不需要 → 直接放 target
而 @Transactional:
只是告诉 Spring:
“事务切面要作用到这个 Bean 上”
六、代理有两种:JDK 和 CGLIB
这是事务底层最常见的一个面试点。
Spring 的选择规则(直接记)
有接口 → JDK 动态代理
没接口 → CGLIB
JDK 动态代理(接口代理)
特点一句话概括:
生成一个“实现接口的假对象”,
所有方法都转发给真实对象执行。
- proxy 实现接口
- 内部持有 target
- pre / post 逻辑在 InvocationHandler 里
CGLIB(子类代理)
特点一句话:
生成目标类的子类,重写方法,在前后插代码。
- proxy 继承 target
- 调用时走
super.xxx() - final / private 方法无法代理
七、为什么 this.xxx() 一定会绕过事务?
这是事务“最经典、最致命”的坑。
先给结论(必须背)
this.xxx() 一定绕过事务,
因为 this 指向的是“目标对象”,不是代理对象。
调用链对比一下就明白了
正确(事务生效)
你
↓
代理对象 proxy
↓
目标对象 target
this 调用(事务失效)
proxy.a()
↓
target.a()
↓
this.b() → target.b()
👉 整个过程没有经过 proxy
而事务逻辑,只存在于 proxy 上。
八、为什么 Spring 不“修复” this 调用问题?
因为这是 Java 语言层面的规则:
- this 在编译期就绑定
- 对象内部方法调用无法被代理拦截
不是 Spring 不想修,是 根本做不到。
九、工程上如何正确写事务代码?
✅ 推荐做法:拆类
@Service
public class OrderService {
@Autowired
private PayService payService;
public void createOrder() {
payService.pay(); // 走代理
}
}
@Service
public class PayService {
@Transactional
public void pay() {}
}
保证调用链:
proxy → proxy
⚠️ 不推荐但要知道的做法
((OrderService) AopContext.currentProxy()).pay();
侵入性强,不优雅,只用于特殊场景。
十、你现在应该形成的“事务心智模型”
以后你看到:
@Transactional
脑子里自动翻译成:
“这个 Bean 会被代理,
事务在代理里,
必须通过代理调用才生效。”
看到:
this.xxx();
条件反射就是一句话:
“我绕过了 Spring AOP。”
十一、一段面试级总结(可直接背)
Spring Boot 的声明式事务是基于 AOP 实现的,Spring 会为命中事务切点的 Bean 创建代理对象,在方法执行前开启事务,在方法正常结束时提交事务,在发生运行时异常时进行回滚。由于事务逻辑存在于代理对象中,因此只有通过代理调用方法时事务才会生效,同类方法内部通过 this 调用会绕过代理,从而导致事务失效。
写在最后
很多人学 Spring 事务,学到的是:
“这个不行、那个不行、这个要注意”
但真正的高手,脑子里只有一句话:
事务 = 代理 + 方法调用路径
一旦你理解了这一点,
90% 的事务问题,都是“调用路径错了”。
更多推荐




所有评论(0)