1. 程式人生 > >Spring宣告式事務管理原始碼解析

Spring宣告式事務管理原始碼解析

核心介面

核心介面
1. PlatformTransactionManager:事務管理器頂級介面:各持久化框架要想接入Spring的事務管理,必須自行提供該介面實現
2. TransactionDefinition:事務的屬性頂級介面:其實現類封裝事務隔離級別、事務傳播性、只讀性等資訊
3. TransactionStatus:事務狀態頂級介面:其實現類封裝事務狀態資訊

宣告式事務流程

說明 :以下流程基於JDBC事務管理,Hibernate或Mybatis事務流程與本流程類似。若流程詳解中程式碼片段有多段,均按執行時呼叫關係貼出,關鍵程式碼處均加上了註釋,從上到下閱讀即可。
Spring資料來源及事務配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"
>
<context:component-scan base-package="com.mercy"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"
/>
</context:component-scan> <!-- 資料來源管理器--> <bean id="dataSource" class="com.mercy.dynamic.DynamicDataSource"></bean> <!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置宣告式事務--> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> <!-- 配置程式設計式事務 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"></property> </bean> </beans>

TransactionInterceptor中的流程

程式碼:

(TransactionInterceptor)
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
            throws Throwable {
    // 獲取事務屬性及事務管理器
    final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        // 開啟事務,返回事務資訊物件(包含事務屬性、事務管理器、事務狀態資訊)
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            // proceedWithInvocation方法中,最終呼叫service處理邏輯
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // 出現異常,繼續判斷是否需要回滾
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            // 重置事務資訊物件
            cleanupTransactionInfo(txInfo);
        }
        // 呼叫service處理後正常返回,進行事務提交
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }
    else {
        // 省略部分程式碼... 
    }
}
  1. 獲取事務屬性及事務管理器,這裡獲取到的事務管理器就是在Spring配置檔案中配置的事務管理器例項
  2. 呼叫事務管理器的getTransaction()方法開啟事務,該方法引數是事務屬性,返回值是事務狀態,並把事務屬性、事務狀態、事務管理器封裝為事務資訊(TransactionInfo)返回,便於後續使用
  3. 呼叫service處理
  4. 若步驟3中丟擲異常
    4.1 判斷異常是否是滿足回滾規則的異常,預設情況下,只有unchecked異常可回滾
    4.2 滿足回滾規則,進行後續回滾處理
    4.3 重置事務資訊物件
  5. 若步驟3中正常返回
    5.1 重置事務資訊物件
    5.2 開始進行提交事務

事務管理器中核心流程 –> 事務攔截器中各個流程的細節

由於我配置的是JDBC事務管理器,這裡也是針對此管理器敘述
1. 開啟事務 –> 事務攔截器的流程2在事務管理器的細節
    1.1 從資料來源中獲取資料庫連線
    1.2 關閉自動提交
    1.3 把獲取到的資料庫連線繫結到當前執行緒
2. 提交事務 –> 事務攔截器的流程5.2在事務管理器的細節
    2.1 從事務狀態物件中獲取資料庫連線
    2.2 提交事務
3. 回滾事務 –> 事務攔截器的流程4.2在事務管理器的細節
    3.1 從事務狀態物件中獲取資料庫連線
    3.2 回滾事務
4. 事務完成後的清理工作,在事務回滾或者事務提交後都會執行,具體時機見下面的詳解程式碼片段
    4.1 解綁與當前執行緒繫結的資料來源
    4.2 重置此次事務使用資料庫連線
    4.3 釋放此次事務使用的資料庫連線,若沒有使用資料庫連線池,否則直接關閉,否則把資料庫連線歸還給連線池

TransactionInterceptor流程2詳解

程式碼1:

(TransactionInterceptor)
protected TransactionInfo createTransactionIfNecessary(
        PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
    // 省略部分程式碼... 

    TransactionStatus status = null;
    if (txAttr != null) {
        if (tm != null) {
            // 通過配置的事務管理器開啟事務,返回事務狀態
            status = tm.getTransaction(txAttr);
        }
        else {
            // 省略部分程式碼... 
        }
    }
    // 把事務屬性、事務管理器、事務狀態封裝為事務資訊返回
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

程式碼2:

(DataSourceTransactionManager)
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
    Object transaction = doGetTransaction();
    // 省略部分程式碼... 
    try {
        boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
        DefaultTransactionStatus status = newTransactionStatus(
                definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
        // 開啟事務
        doBegin(transaction, definition);
        prepareSynchronization(status, definition);
        return status;
    }
    // 省略部分程式碼... 
}

程式碼3:

(DataSourceTransactionManager)
protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;

    try {
        if (txObject.getConnectionHolder() == null ||
                txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            // 通過資料來源獲取資料庫連線
            Connection newCon = this.dataSource.getConnection();
            if (logger.isDebugEnabled()) {
                logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }

        // 省略部分程式碼... 

        // 關閉自動提交
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (logger.isDebugEnabled()) {
                logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
            }
            con.setAutoCommit(false);
        }

        // 省略部分程式碼... 

        // 把資料庫連線繫結到當前執行緒
        // Bind the connection holder to the thread.
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
        }
    }
    // 省略部分程式碼... 
}

執行至此然後返回,整個事務開啟完成。這裡開啟事務的核心就是從配置的資料來源中獲取一個數據庫連線,關閉自動提交,然後把這個資料庫連線繫結到當前執行緒,供後續使用

TransactionInterceptor流程4詳解(呼叫service過程中出異常)

程式碼1:

(TransactionInterceptor)
protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.hasTransaction()) {
        // 省略部分程式碼... 

        // 判斷異常是否是滿足回滾規則的異常,預設情況下,只有unchecked異常可回滾
        if (txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                // 從事務資訊物件獲取事務管理器物件,進行事務回滾
                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
            }
            // 省略部分程式碼... 
        }
        else {
            // 省略部分程式碼... 
        }
    }
}

這裡核心就是判斷異常是否滿足回滾規則,若滿足則進行後續回滾

程式碼2:

(DataSourceTransactionManager)
private void processRollback(DefaultTransactionStatus status) {
    try {
        try {
            triggerBeforeCompletion(status);
            if (status.hasSavepoint()) {
                // 省略部分程式碼... 
            }
            else if (status.isNewTransaction()) {
                // 省略部分程式碼... 

                // 通過配置的事務管理器進行回滾
                doRollback(status);
            }
            // 省略部分程式碼... 
        }
        // 省略部分程式碼... 
    }
    finally {
        // 執行回滾的後需清理工作
        cleanupAfterCompletion(status);
    }
}

這裡先通過事務管理器進行回滾,回滾操作完成後進行清理工作,清理細節見後續程式碼4

程式碼3:

(DataSourceTransactionManager)
protected void doRollback(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    // 獲取之前已開啟事務的資料庫連線
    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        // 回滾
        con.rollback();
    }
    catch (SQLException ex) {
        throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
    }
}

這裡獲取之前開啟事務的資料庫連線進行回滾

程式碼4:

(DataSourceTransactionManager)
protected void doCleanupAfterCompletion(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;

    // 解綁與當前執行緒繫結的資料來源
    if (txObject.isNewConnectionHolder()) {
        TransactionSynchronizationManager.unbindResource(this.dataSource);
    }

    // 重置資料庫連線
    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        if (txObject.isMustRestoreAutoCommit()) {
            con.setAutoCommit(true);
        }
        DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
    }
    catch (Throwable ex) {
        logger.debug("Could not reset JDBC Connection after transaction", ex);
    }

    if (txObject.isNewConnectionHolder()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
        }
        // 釋放資料庫連線
        DataSourceUtils.releaseConnection(con, this.dataSource);
    }

    txObject.getConnectionHolder().clear();
}

回滾後的清理工作:1.解綁與當前執行緒繫結的資料庫連線 2.重置資料庫連線 3.釋放資料庫連線

TransactionInterceptor流程5詳解(呼叫service過程正常返回)

程式碼1:

(TransactionInterceptor)
protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
    if (txInfo != null && txInfo.hasTransaction()) {
        // 從事務資訊物件中獲取事務管理器,進行後續提交
        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    }
}

程式碼2:

(DataSourceTransactionManager)
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
    try {
        boolean beforeCompletionInvoked = false;
        try {
            // 省略部分程式碼... 
            if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction commit");
                }
                // 執行提交
                doCommit(status);
            }
            // 省略部分程式碼... 
        }
        // 省略部分程式碼... 
    }
    finally {
        // 提交後的清理工作
        cleanupAfterCompletion(status);
    }
}

事務管理器進行事務提交,然後進行清理工作

程式碼3:

(DataSourceTransactionManager)
protected void doCommit(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    // 獲取之前開啟事務的資料庫連線
    Connection con = txObject.getConnectionHolder().getConnection()

    try {
        // 提交事務
        con.commit();
    }
    // 省略部分程式碼... 
}

獲取之前開啟了事務的資料庫連線進行提交

程式碼4:

(DataSourceTransactionManager)
protected void doCleanupAfterCompletion(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;

    // 解綁與當前執行緒繫結的資料來源
    if (txObject.isNewConnectionHolder()) {
        TransactionSynchronizationManager.unbindResource(this.dataSource);
    }

    // 重置資料庫連線
    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        if (txObject.isMustRestoreAutoCommit()) {
            con.setAutoCommit(true);
        }
        DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
    }
    catch (Throwable ex) {
        logger.debug("Could not reset JDBC Connection after transaction", ex);
    }

    if (txObject.isNewConnectionHolder()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
        }
        // 釋放資料庫連線
        DataSourceUtils.releaseConnection(con, this.dataSource);
    }

    txObject.getConnectionHolder().clear();
}

提交後的清理工作:1.解綁與當前執行緒繫結的資料庫連線 2.重置資料庫連線 3.釋放資料庫連線