首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Spring 聲明式事務管理源碼深度解讀(二):事務提交與回滾全流程分析

     編輯:Java 相關知識

前言:承接事務開始,聚焦提交/回滾邊界控制

上一篇分析了事務如何在方法入棧時創建/加入,本文聚焦方法出棧時的 事務邊界控制:提交、回滾、保存點釋放、線程上下文清理。核心入口仍是 TransactionInterceptor.invoke()。

TransactionInterceptor.invoke() 完整邏輯回顧

public Object invoke(MethodInvocation invocation) throws Throwable {
    Class targetClass = (invocation.getThis() != null) ? invocation.getThis().getClass() : null;
    TransactionInfo txInfo = createTransactionIfNecessary(invocation.getMethod(), targetClass);
    Object retVal = null;
    try {
        retVal = invocation.proceed(); // 執行業務方法 + 後續攔截器
    } catch (Throwable ex) {
        completeTransactionAfterThrowing(txInfo, ex); // 異常路徑:決定回滾/提交
        throw ex;
    } finally {
        cleanupTransactionInfo(txInfo); // 必執行:清理線程綁定的 TransactionInfo
    }
    commitTransactionAfterReturning(txInfo); // 正常路徑:提交事務
    return retVal;
}

關鍵觀察:cleanupTransactionInfo() 在 finally 塊中,保證無論正常/異常/返回,線程綁定的 TransactionInfo 棧都會正確彈出。

TransactionInfo 棧機制:巢狀事務上下文管理的核心

bindToThread() 入棧邏輯

private void bindToThread() {
    oldTransactionInfo = (TransactionInfo) currentTransactionInfo.get(); // 保存舊棧頂
    currentTransactionInfo.set(this); // 當前棧頂 = this
}

currentTransactionInfo 是 ThreadLocal。

cleanupTransactionInfo() 出棧邏輯

protected void cleanupTransactionInfo(TransactionInfo txInfo) {
    if (txInfo == null) return;
    if (txInfo.oldTransactionInfo != null) {
        currentTransactionInfo.set(txInfo.oldTransactionInfo); // 恢復父事務上下文
    } else {
        currentTransactionInfo.remove(); // 棧空,移除 ThreadLocal
    }
}

場景演示:A(REQUIRED) → B(REQUIRED)

  1. A 入棧:創建 TxInfo_A,old=null,綁定線程。
  2. B 入棧:創建 TxInfo_B,old=TxInfo_A,綁定線程(遮蓋 A)。
  3. B 出棧:cleanup(TxInfo_B) → 恢復線程綁定為 TxInfo_A。不提交事務(見下文)。
  4. A 出棧:cleanup(TxInfo_A) → 移除線程綁定。提交事務。

這完美實現了 事務上下文的巢狀棧語義,且與事務實體(Connection/Session)分離——TransactionInfo 只是輔助類,真實事務狀態在 TransactionStatus 中。

正常提交路徑:commitTransactionAfterReturning()

protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
    if (txInfo != null && txInfo.hasTransaction()) {
        if (logger.isDebugEnabled()) logger.debug("Invoking commit for transaction on " + txInfo.joinpointIdentification());
        this.transactionManager.commit(txInfo.getTransactionStatus());
    }
}

委託 PlatformTransactionManager.commit(TransactionStatus)。

AbstractPlatformTransactionManager.commit() 模板方法

public final void commit(TransactionStatus status) throws TransactionException {
    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    if (defStatus.isCompleted()) throw new IllegalTransactionStateException("Transaction already completed");
    if (defStatus.isLocalRollbackOnly()) { processRollback(defStatus); return; }
    if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
        processRollback(defStatus);
        throw new UnexpectedRollbackException("Transaction rolled back: marked rollback-only");
    }
    processCommit(defStatus);
}

關鍵判斷:

  • isCompleted():防止重複提交/回滾。
  • isLocalRollbackOnly():代碼顯式調用 setRollbackOnly()。
  • isGlobalRollbackOnly():外部事務管理器(JTA)或嵌套事務標記全局回滾。

processCommit() 核心提交邏輯

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
    try {
        boolean beforeCompletionInvoked = false;
        try {
            triggerBeforeCommit(status);   // 同步回調:beforeCommit
            triggerBeforeCompletion(status); // 同步回調:beforeCompletion
            beforeCompletionInvoked = true;
            
            if (status.hasSavepoint()) { // 嵌套事務:釋放保存點
                status.releaseHeldSavepoint();
            } else if (status.isNewTransaction()) { // 真正的新事務:提交
                boolean globalRollbackOnly = status.isGlobalRollbackOnly();
                doCommit(status); // 子類實現:JDBC con.commit()、Hibernate tx.commit()
            }
            // 後續:觸發 afterCommit、afterCompletion 同步回調
        } catch (Throwable ex) {
            // 異常處理:觸發 afterCompletion、拋出異常
        }
    } finally {
        cleanupAfterCompletion(status); // 清理同步資源
    }
}

關鍵判斷:status.isNewTransaction()

這是 區分「外層事務提交」與「內層事務不提交」的分水嶺:

  • 外層事務(A):newTransactionStatus(definition, transaction, true, ...) → isNewTransaction() = true → 執行 doCommit()。
  • 內層事務(B,REQUIRED 傳播):進入 handleExistingTransaction() → 返回 newTransactionStatus(definition, transaction, false, ...) → isNewTransaction() = false → 跳過 doCommit(),僅釋放保存點(如有)。

handleExistingTransaction():現有事務的傳播行為處理

private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled) {
    switch (definition.getPropagationBehavior()) {
        case PROPAGATION_NEVER: throw new IllegalTransactionStateException("Existing transaction found");
        case PROPAGATION_NOT_SUPPORTED: 
            Object suspended = suspend(transaction); // 掛起現有事務
            return newTransactionStatus(definition, null, false, ..., suspended);
        case PROPAGATION_REQUIRES_NEW:
            Object suspended = suspend(transaction);
            doBegin(transaction, definition); // 開啟新事務
            return newTransactionStatus(definition, transaction, true, ..., suspended);
        case PROPAGATION_NESTED:
            if (!isNestedTransactionAllowed()) throw new NestedTransactionNotSupportedException();
            if (useSavepointForNestedTransaction()) {
                DefaultTransactionStatus status = newTransactionStatus(definition, transaction, false, false, ...);
                status.createAndHoldSavepoint(); // 設置保存點
                return status;
            } else {
                doBegin(transaction, definition); // 真正嵌套事務(JTA)
                return newTransactionStatus(definition, transaction, true, ..., null);
            }
        default: // PROPAGATION_SUPPORTS / REQUIRED
            return newTransactionStatus(definition, transaction, false, ..., null); // 共用現有事務
    }
}

這裡的 newTransactionStatus(..., false, ...) 直接決定了內層方法 不會觸發真實提交。

異常回滾路徑:completeTransactionAfterThrowing()

protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.hasTransaction()) {
        if (txInfo.transactionAttribute.rollbackOn(ex)) { // 判斷異常是否觸發回滾
            try {
                this.transactionManager.rollback(txInfo.getTransactionStatus());
            } catch (TransactionSystemException ex2) {
                logger.error("Rollback failed", ex2);
            }
        } else {
            // 異常不觸發回滾:嘗試提交(如樂觀鎖異常)
            try {
                this.transactionManager.commit(txInfo.getTransactionStatus());
            } catch (TransactionSystemException ex2) {
                logger.error("Commit failed", ex2);
            }
        }
    }
}

rollbackOn(ex) 默認對 RuntimeException/Error 返回 true,受檢異常返回 false,可通過 @Transactional(rollbackFor/ noRollbackFor) 自定義。

具體事務管理器提交/回滾實現:DataSourceTransactionManager

doCommit()

protected void doCommit(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    Connection con = txObject.getConnectionHolder().getConnection();
    if (status.isDebug()) logger.debug("Committing JDBC transaction on Connection [" + con + "]");
    try {
        con.commit(); // 核心:JDBC 原生提交
    } catch (SQLException ex) {
        throw new TransactionSystemException("Could not commit JDBC transaction", ex);
    }
}

doRollback()

protected void doRollback(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    Connection con = txObject.getConnectionHolder().getConnection();
    if (status.isDebug()) logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
    try {
        con.rollback(); // 核心:JDBC 原生回滾
    } catch (SQLException ex) {
        throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
    }
}

事務同步清理:cleanupAfterCompletion()

private void cleanupAfterCompletion(DefaultTransactionStatus status) {
    status.setCompleted(); // 標記完成
    if (status.isNewSynchronization()) {
        TransactionSynchronizationManager.clear(); // 清理 ThreadLocal 綁定的資源、同步回調
    }
    if (status.isNewTransaction()) {
        doCleanupAfterCompletion(status.getTransaction()); // 子類清理:解綁 ConnectionHolder、關閉 Session
    }
}

總結:事務提交/回滾的完整決策樹

方法出棧
    ↓
finally → cleanupTransactionInfo() [恢復 TransactionInfo 棧]
    ↓
正常返回 → commitTransactionAfterReturning()
    ↓
txInfo.hasTransaction()?
    ├─ 否 → 結束
    └─ 是 → transactionManager.commit(status)
              ↓
        status.isCompleted()? → 異常
        status.isLocalRollbackOnly()? → rollback
        status.isGlobalRollbackOnly() && !shouldCommitOnGlobalRollbackOnly()? → rollback + UnexpectedRollbackException
        ↓
        processCommit()
              ↓
        triggerBeforeCommit/BeforeCompletion 同步回調
              ↓
        status.hasSavepoint()? → releaseSavepoint (嵌套事務)
        status.isNewTransaction()? → doCommit() (真實提交)
              ↓
        triggerAfterCommit/AfterCompletion 同步回調
              ↓
        cleanupAfterCompletion() [清理同步資源、解綁 Connection/Session]
    ↓
異常拋出 → completeTransactionAfterThrowing()
    ↓
txInfo.hasTransaction()?
    ├─ 否 → 結束
    └─ 是 → rollbackOn(ex)?
              ├─ 是 → rollback()
              └─ 否 → commit() (非回滾異常嘗試提交)

核心洞見

  1. TransactionInfo 棧 ≠ 事務實體棧:前者管理上下文可見性,後者由 isNewTransaction() 決定是否真實提交。
  2. REQUIRED 傳播不創建新事務:內層 TransactionStatus.isNewTransaction() = false,提交邏輯自動跳過 doCommit()。
  3. 保存點機制支援 NESTED:status.createAndHoldSavepoint() / releaseHeldSavepoint() 實現 JDBC 3.0 Savepoint 語義。
  4. 同步回調機制:TransactionSynchronization 允許在提交/回滾前後掛鉤(Hibernate flush、緩存失效、郵件發送等)。
  5. 異常分類決定回滾:默認僅非受檢異常回滾,受檢異常需顯式配置 rollbackFor。

理解這套機制,能讓你精準控制事務邊界,排查「事務未提交」、「意外回滾」、「嵌套事務行為不符預期」等生產疑難雜症。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved