基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

声明式事务(@Transactional)的底层运行原理是什么?

知识点图片

Spring 声明式事务(@Transactional)的底层运行原理,可以高度概括为:Spring AOP(动态代理) + ThreadLocal + 数据库事务本身的机制

它将底层的数据库连接管理、事务提交/回滚操作与业务代码解耦,让开发者只需关注业务逻辑。

以下是其底层运行原理的详细拆解:

一、 核心组件与基础机制

  1. Spring AOP(动态代理)
    当你在类或方法上添加 @Transactional 注解时,Spring IoC 容器在初始化该 Bean 时,会检测到这个注解。此时,Spring 不会把原始对象放入容器,而是利用 AOP 机制生成一个代理对象(Proxy)

    • 如果类实现了接口,默认使用 JDK 动态代理
    • 如果类没有实现接口(或者在 Spring Boot 2.x 之后强制配置),则使用 CGLIB 动态代理
  2. TransactionInterceptor(事务拦截器)
    代理对象中织入的“增强逻辑”就是 TransactionInterceptor。当外部调用代理对象的方法时,请求会被这个拦截器拦截,从而在业务方法执行前后加入事务控制逻辑(即开启事务、提交/回滚事务)。

  3. ThreadLocal(线程绑定)
    为了保证同一个事务中(如 Controller 调用 Service,Service 又调用多个 DAO),所有的数据库操作使用的是同一个数据库连接,Spring 底层使用 TransactionSynchronizationManager(内部封装了 ThreadLocal)将 Connection 绑定到当前线程上。


二、 底层执行的完整生命周期(6步走)

当客户端调用带有 @Transactional 的方法时,底层执行流程如下:

1. 拦截方法调用

外部调用该方法时,实际上调用的是代理对象。代理对象将请求转发给 TransactionInterceptor

2. 解析事务属性

拦截器会读取 @Transactional 注解上的属性配置,比如:传播行为(Propagation)、隔离级别(Isolation)、超时时间(Timeout)、只读标记(ReadOnly)以及回滚规则(RollbackFor)。

3. 获取数据库连接并开启事务

  • 拦截器调用 PlatformTransactionManager(如 DataSourceTransactionManager)。
  • 从数据库连接池(如 HikariCP、Druid)中获取一个 Connection
  • 核心动作:执行 connection.setAutoCommit(false),关闭数据库的自动提交机制,这标志着物理事务的开启
  • 将该 Connection 与当前线程绑定(存入 ThreadLocal 中)。

4. 执行业务逻辑(目标方法)

拦截器通过反射(或 CGLIB 的 MethodProxy)调用实际的目标方法(即你写的业务代码)。此时,业务代码中所有的 DAO 操作,底层都会通过 ThreadLocal 拿到同一个 Connection 来执行 SQL。

5. 决定提交或回滚(核心判断)

目标方法执行完毕,流程回到拦截器中:

  • 如果执行成功(没有抛出异常)
    拦截器调用 TransactionManager,执行 connection.commit(),将数据持久化到数据库。
  • 如果抛出异常
    拦截器会捕获异常,并判断该异常是否满足回滚条件(默认情况下,只有 RuntimeException 和 Error 会触发回滚,Checked Exception 不会引发回滚)。
    • 满足条件:执行 connection.rollback()
    • 不满足条件:依然执行 connection.commit()

6. 资源清理

无论提交还是回滚,最后一步都会清理资源:

  • 恢复连接的默认状态(connection.setAutoCommit(true))。
  • 将连接从 ThreadLocal 中解绑。
  • 将连接归还给数据库连接池。

三、 伪代码演示拦截器原理

为了更好地理解,可以看看 TransactionInterceptor 底层逻辑的极致精简版伪代码:

java
public Object invoke(MethodInvocation invocation) throws Throwable {
    // 1. 获取事务管理器、事务属性等配置
    PlatformTransactionManager tm = getTransactionManager();
    TransactionAttribute txAttr = getTransactionAttribute();
    
    // 2. 开启事务 (获取连接,设置 autoCommit = false,绑定到 ThreadLocal)
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr);
    
    Object retVal = null;
    try {
        // 3. 执行目标业务方法
        retVal = invocation.proceed();
    } catch (Throwable ex) {
        // 4. 出现异常,判断是否需要回滚
        if (txAttr.rollbackOn(ex)) {
            tm.rollback(txInfo.getTransactionStatus());
        } else {
            tm.commit(txInfo.getTransactionStatus());
        }
        throw ex;
    } finally {
        // 6. 恢复线程状态,清理 ThreadLocal
        cleanupTransactionInfo(txInfo);
    }
    
    // 5. 目标方法成功执行,提交事务
    tm.commit(txInfo.getTransactionStatus());
    return retVal;
}

四、 深入理解:为什么 @Transactional 会失效?

基于上述的底层原理(动态代理拦截器异常捕获),我们可以非常清晰地解释为什么日常开发中事务经常会“失效”:

  1. 同类中方法内部调用(Self-Invocation)
    • 现象:在同一个类中,方法 A(无事务)直接调用方法 B(有 @Transactional),事务不生效。
    • 原理:直接调用 this.B() 使用的是原生对象,没有经过代理对象,自然无法触发 TransactionInterceptor 的拦截。
  2. 方法非 public
    • 现象@Transactional 标注在 privateprotected 或包级私有方法上失效。
    • 原理:Spring AOP 规范要求只能对 public 方法进行代理。拦截器底层在获取事务属性时,如果发现修饰符不是 public 会直接忽略。
  3. 异常被 try-catch 吞了
    • 现象:方法内部抓住了异常没有抛出,事务没有回滚。
    • 原理TransactionInterceptor 是在外层通过 catch 捕获异常来决定是否回滚的。如果异常在业务代码内部被 catch 且没有 throw,拦截器以为方法是正常结束的,于是执行了 commit()
  4. 抛出了 Checked Exception(如 IOException)
    • 现象:抛出异常了,但事务却提交了。
    • 原理:Spring 默认只在遇到 RuntimeExceptionError 时回滚。可以通过 @Transactional(rollbackFor = Exception.class) 来改变此默认行为。
00:00
00:00