一文吃透 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% 的事务问题,都是“调用路径错了”

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐