全局事务入口感知子事务方法-TCC

一、核心前提:两个注解的分工

注解 定位 核心作用 执行时机
@GlobalTransactional 全局事务入口 标记 "主方法",创建全局事务 XID,管控整体提交 / 回滚 主方法执行前
@TwoPhaseBusinessAction TCC 子方法 标记 "Try 方法",绑定 Confirm/Cancel,声明为 TCC 分支 子方法执行时(被主方法调用)

简单说:@GlobalTransactional 是 "总指挥",@TwoPhaseBusinessAction 是 "小兵"------ 总指挥不会提前知道有哪些小兵,而是小兵执行时主动向总指挥 "报到"。

二、完整流程:从主方法执行到子方法绑定

步骤 1:拦截 @GlobalTransactional,创建全局事务(主方法执行前)

Seata 通过 GlobalTransactionalInterceptor 拦截所有带 @GlobalTransactional 的主方法,核心逻辑:

java

运行

复制代码
// GlobalTransactionalInterceptor.java
public Object invoke(MethodInvocation invocation) throws Throwable {
    // 1. 检查是否有全局事务 XID(首次执行时为 null)
    String xid = RootContext.getXID();
    if (xid == null) {
        // 2. 创建全局事务,生成唯一 XID(如:127.0.0.1:8091:123456)
        GlobalTransaction tx = GlobalTransactionContext.createNew();
        // 3. 开启全局事务(更新 global_table 状态为 "开始")
        tx.begin(/* 超时时间 */, /* 事务名称 */);
        // 4. 将 XID 绑定到当前线程(ThreadLocal)
        RootContext.bind(tx.getXid());
    }

    try {
        // 5. 执行主方法(此时会调用所有带 @TwoPhaseBusinessAction 的子方法)
        return invocation.proceed();
    } catch (Exception e) {
        // 6. 异常时回滚全局事务(触发 Cancel 方法)
        GlobalTransactionContext.getCurrent().rollback();
        throw e;
    } finally {
        // 7. 无异常则提交全局事务(触发 Confirm 方法)
        if (RootContext.getXID() != null) {
            GlobalTransactionContext.getCurrent().commit();
            RootContext.unbind(); // 清除线程绑定的 XID
        }
    }
}

关键:此时仅创建了全局事务 XID 并绑定到当前线程,还不知道任何 TCC 子方法的存在。

步骤 2:拦截 @TwoPhaseBusinessAction,注册分支事务(子方法执行时)

当主方法执行到带 @TwoPhaseBusinessAction 的子方法时,Seata 通过 TccActionInterceptor 拦截该子方法,核心逻辑:

java

运行

复制代码
// TccActionInterceptor.java
public Object invoke(MethodInvocation invocation) throws Throwable {
    // 1. 检查当前线程是否有全局事务 XID(主方法已绑定)
    String xid = RootContext.getXID();
    if (xid == null) {
        // 无全局事务:直接执行方法(非 TCC 分支,仅普通方法)
        return invocation.proceed();
    }

    // 2. 解析 @TwoPhaseBusinessAction 注解(绑定 Confirm/Cancel 方法)
    Method tryMethod = invocation.getMethod();
    TwoPhaseBusinessAction tccAnnotation = tryMethod.getAnnotation(TwoPhaseBusinessAction.class);
    TccAction tccAction = TccActionParser.parse(tryMethod); // 解析出 Try/Confirm/Cancel 方法

    // 3. 注册 TCC 分支事务(核心:将子方法关联到全局事务 XID)
    BranchRegistration branchReg = new BranchRegistration();
    branchReg.setXid(xid);
    branchReg.setBranchType(BranchType.TCC);
    branchReg.setResourceId(/* TCC 方法唯一标识:类名+方法名 */);
    branchReg.setApplicationData(/* 序列化 TccAction 对象(含 Confirm/Cancel) */);
    // 调用 Seata Server 注册分支,返回分支 ID
    Long branchId = TccResourceManager.INSTANCE.branchRegister(branchReg);

    try {
        // 4. 执行 Try 方法(业务逻辑:如冻结金额、预留资源)
        Object result = invocation.proceed();
        // 5. 标记分支事务"Try 阶段完成"
        TccResourceManager.INSTANCE.branchReport(xid, branchId, BranchStatus.PhaseOne_Done);
        return result;
    } catch (Exception e) {
        // 6. Try 失败:标记分支状态,后续全局事务会触发 Cancel
        TccResourceManager.INSTANCE.branchReport(xid, branchId, BranchStatus.PhaseOne_Failed);
        throw e;
    }
}

核心关键

  • 子方法执行时,通过 ThreadLocal 获取主方法绑定的 XID,确认自己属于哪个全局事务;
  • 解析 @TwoPhaseBusinessAction 注解,将 Try/Confirm/Cancel 方法序列化后,注册为 "TCC 分支事务",并写入 Seata 数据库的 branch_table(关联 XID);
  • 此时,全局事务(主方法)就通过 branch_table 知道了 "自己有哪些 TCC 子方法"。
步骤 3:全局事务提交 / 回滚,触发二阶段方法(主方法执行后)

当主方法执行完成(无异常 / 有异常),@GlobalTransactional 触发提交 / 回滚逻辑,核心是branch_table 读取已注册的 TCC 分支

java

运行

复制代码
// DefaultGlobalTransaction.java
public void commit() throws TransactionException {
    // 1. 根据 XID 查询所有已注册的 TCC 分支(来自步骤 2 的注册)
    List<BranchDO> branchList = branchStorage.findBranchListByXid(this.xid);
    
    for (BranchDO branch : branchList) {
        if (branch.getBranchType() == BranchType.TCC) {
            // 2. 反序列化 TccAction 对象(获取 Confirm 方法)
            TccAction tccAction = JSON.parseObject(branch.getApplicationData(), TccAction.class);
            // 3. 反射执行 Confirm 方法(子方法的二阶段)
            invokeTargetMethod(tccAction.getConfirmMethod(), branch.getArgs());
            // 4. 更新分支状态为"已提交"
            branchStorage.updateBranchStatus(branch.getBranchId(), BranchStatus.PhaseTwo_Committed);
        }
    }
    // 5. 更新全局事务状态为"已提交"
    globalStorage.updateGlobalStatus(this.xid, GlobalStatus.Committed);
}

回滚逻辑同理:从 branch_table 读取分支,执行 Cancel 方法。

三、核心原理总结(一句话说透)

@GlobalTransactional 不会 "主动扫描" 子方法的 @TwoPhaseBusinessAction,而是:

  1. 主方法执行前,创建全局事务 XID 并绑定到当前线程;
  2. 子方法执行时,Seata 拦截 @TwoPhaseBusinessAction,通过线程绑定的 XID 向全局事务 "注册自己";
  3. 全局事务提交 / 回滚时,从数据库读取已注册的 TCC 分支,执行二阶段方法。

四、关键细节(避免误解)

  1. 线程绑定是核心桥梁 :XID 存储在 RootContext(本质是 ThreadLocal<String>),子方法和主方法在同一个线程执行,因此能拿到同一个 XID,这是 "关联" 的基础。
  2. 非侵入式拦截 :无需在主方法中显式声明子方法,只要子方法加 @TwoPhaseBusinessAction 且被主方法调用,就会自动注册为分支 ------ 这是 Seata TCC 无侵入的关键。
  3. 分支注册是持久化保障 :TCC 分支信息写入 branch_table,即使 Seata Server 宕机,恢复后仍能根据 XID 找到分支方法,保证二阶段执行(幂等性由业务层保证)。

五、调试验证(快速确认绑定关系)

  1. 断点 1:GlobalTransactionalInterceptor.invoke() → 查看 XID 的创建和绑定;

  2. 断点 2:TccActionInterceptor.invoke() → 查看子方法如何解析 @TwoPhaseBusinessAction 并注册分支;

  3. 数据库验证:执行主方法后,查询 branch_table

    sql

    复制代码
    SELECT xid, resource_id, application_data FROM branch_table WHERE branch_type = 'TCC';

    application_data 字段会序列化存储 Try/Confirm/Cancel 方法的全限定名,可直接看到主方法(XID)和子方法的绑定关系。

最终类比

  • @GlobalTransactional 像 "项目经理",启动项目(创建 XID)后开始干活;
  • @TwoPhaseBusinessAction 像 "员工",干活时(执行子方法)主动向项目经理报到(注册分支);
  • 项目收尾时(提交 / 回滚),项目经理根据报到记录(branch_table)安排员工做收尾工作(Confirm/Cancel)。
相关推荐
laozhoy13 小时前
深入理解Go语言errors.As方法:灵活的错误类型识别
开发语言·后端·golang
周杰伦_Jay3 小时前
【Go 语言】核心特性、基础语法及面试题
开发语言·后端·golang
周杰伦_Jay3 小时前
【Python开发面试题及答案】核心考点+原理解析+实战场景
开发语言·python
前端不太难3 小时前
RN Hooks 设计规范与反模式清单
开发语言·php·设计规范
HyperAI超神经3 小时前
【vLLM 学习】vLLM TPU 分析
开发语言·人工智能·python·学习·大语言模型·vllm·gpu编程
Leon-Ning Liu3 小时前
Oracle 19c RAC报错ORA-17503,ORA-27300,ORA-27301,ORA-27302
数据库·oracle
爱笑的眼睛113 小时前
FastAPI 请求验证:超越 Pydantic 基础,构建企业级验证体系
java·人工智能·python·ai
czlczl200209254 小时前
Spring Boot 参数校验进阶:抛弃复杂的 Group 分组,用 @AssertTrue 实现“动态逻辑校验”
java·spring boot·后端
得物技术4 小时前
Java 设计模式:原理、框架应用与实战全解析|得物技术
java