Skip to content

02.3 — @Transactional 原理

定位: @Transactional 注解的 AOP 实现原理和同方法调用失效问题 面试高频度: ⭐⭐⭐⭐⭐ 考查方式: @Transactional 底层机制、失效场景、事务传播

一、这是什么?为什么需要它?

@Transactional 是声明式事务的核心注解。它基于 AOP 代理机制,在方法执行前后自动创建/提交/回滚事务,让开发者无需编写事务管理代码。

二、原理拆解

2.1 @Transactional 执行流程

调用 Bean 方法
    -> 代理对象? -> 是
        -> TransactionInterceptor (@Around 环绕通知)
            -> 获取事务属性
            -> PlatformTransactionManager.getTransaction
            -> 执行目标方法
            -> 成功: commit | 异常: rollback
            -> 返回结果
    -> 否 (直接调用原始对象) -> 事务不生效!

核心流程

  1. TransactionInterceptor 实现 MethodInterceptor
  2. 在 invoke() 方法中,根据 @Transactional 配置获取事务属性
  3. 调用 PlatformTransactionManager.getTransaction() 创建/加入事务
  4. 通过反射执行目标方法
  5. 无异常 -> commit();异常 -> rollback()

2.2 同方法内 @Transactional 失效

java
@Service
public class UserService {
    
    @Transactional
    public void methodA() {
        // 事务生效 — 因为通过代理调用
        methodB();  // B 上的事务不生效!
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // 这里的事务配置不会生效
        // 因为 methodB 是通过 this.methodB() 调用的,不是代理对象
    }
}

原因

Spring AOP 基于代理,方法内 this.methodB() 调用的是原始对象的方法,而非代理对象,代理拦截未生效。

三种解决方案

方案代码优缺点
AopContext.currentProxy()((UserService) AopContext.currentProxy()).methodB()需要 @EnableAspectJAutoProxy(exposeProxy = true)
自注入@Autowired self; self.methodB()造成循环依赖(可用 @Lazy 缓解)
拆分为两个 Service将 B 移到另一个 Service 类最干净,但增加类数量

同样的问题也影响 @Async

java
@Service
public class NotificationService {
    public void sendBatch() {
        this.sendEmail();  // @Async 不生效!
    }
    @Async
    public void sendEmail() { ... }
}

三、面试视角

追问答案要点
@Transactional 在 private 方法上为什么不生效?因为 CGLIB/JDK 无法代理 private 方法
同方法调用 @Transactional 什么情况不生效?this.method() 调用原始对象,代理不拦截
三种解决方案哪个最好?拆分为两个 Service 最干净
@Transactional 默认回滚什么异常?RuntimeException 和 Error,checked exception 不回滚

📚 相关链接

  • **事务管理板块** — 深入学习事务传播行为和源码
  • **JDK动态代理与CGLIB** — 为什么需要代理才能拦截
  • ← 返回 **返回 AOP 索引**

Knowledge4J — Java 知识库