DataSourceUtils源碼分析


1. 作用

看名字就能知道這個類是對DataSource的一個封裝,這個類提供了一系列操作數據庫連接的工具方法。這個類在Spring事務中非常重要,最主要的作用就是提供了能夠從當前線程獲取開啟事務時綁定的連接。其中Spring Jdbc里的JdbcTemplate類就是采用DataSourceUtils.getConnection()方法獲取連接的。

2. TransactionSynchronization

在分析DataSourceUtils的方法前,先簡單介紹下這個接口吧。其實對於這個接口應該會很眼熟,應為在分析事務源碼的時候,這個接口出現的頻率很高。在事務的suspend、resume、rollback、commit方法中會觸發這個接口提供的鈎子。下面看下這個接口的定義

public interface TransactionSynchronization extends Flushable {

	/** Completion status in case of proper commit */
	int STATUS_COMMITTED = 0;

	/** Completion status in case of proper rollback */
	int STATUS_ROLLED_BACK = 1;

	/** Completion status in case of heuristic mixed completion or system errors */
	int STATUS_UNKNOWN = 2;

	/**
	 * 此鈎子在事務中的suspend方法觸發。
	 * 這個鈎子意味者如果自己在事務運行期間在當前線程綁定過資源,可能需要移除掉。
	 * 想象下事務里掛起的時候,會移除掉開啟事務時綁定的connectionHolder.
	 */
	void suspend();

	/**
	 * 與suspend相反,在事務中的resume方法觸發
	 */
	void resume();

	/**
	 * 此鈎子在事務提交前執行
	 * 在調用commit方法時觸發,之后再走commit邏輯。我們知道事務實際的結果還是需要根據
	 * commit方法來決定,因為即使調用了commit方法也仍然可能會執行回滾。
	 * 如果按照正常流程走提交的話,我們通過這個鈎子方法由機會在事務真正提交前還能對數據庫做一些操作。
	 */
	void beforeCommit(boolean readOnly);

	/**
	 * 此鈎子在事務完成前執行(發生在beforeCommit之后)
	 * 調用commit或者rollback都能觸發,此鈎子在真正提交或者真正回滾前執行。
	 */
	void beforeCompletion();

	/**
	 * 事務真正提交后執行,這個鈎子執行意味着事務真正提交了。
	 */
	void afterCommit();

	/**
	 * 事務完成后執行
	 * 可能是真正提交了,也有可能是回滾
	 */
	void afterCompletion(int status);

}

3. TransactionSynchronizationManager

這個類也簡單提下吧,因為方法里的代碼都很簡單,這個類的作用就是綁定資源到當前線程、注冊TransactionSynchronization接口、綁定事務的各個屬性。

主要注意的點就是isSynchronizationActive方法以及isActualTransactionActive方法。前者代表事務同步是否激活,如果激活了才可以注冊TransactionSynchronization接口事件。后者代表當前線程是否存在真實的事務。有前面事務分析知道,Spring開啟一個空事務時,isSynchronizationActive() = true,而isActualTransactionActive = false,因為不存在真實的事務。

4. 方法解析

4.1 獲取連接(getConnection)

先簡單介紹這個方法的作用,此方法的目的就是獲取一個連接。連接的處理行為有兩種

  • 此連接由Spring事務管理,無論是空事務還是真實事務,只要此方法是在getTransaction()之后,在commit/rollback方法之前調用的。如果此事務是一個空事務,那么此方法會獲取一個連接,並且會綁定到當前線程,同時會注冊一個ConnectionSynchronization。當事務運行期間調用commit /rollback時便會觸發ConnectionSynchronizationbeforeCompletionafterCompletion鈎子來解綁線程中綁定的連接,然后釋放掉。如果此事務是一個真實事務,那么此方法拿到的是開啟事務時按照TransactionDefinition定義好的帶事務連接,這個連接會綁定到當前線程,然后這個方法從當前線程獲取。這個帶事務的連接由事務管理器在事務真正回滾或提交后自動解綁釋放。
  • 完全由調用者管理,與Spring事務無關,需要手動調用releaseConnection方法釋放。

注: 以上結論由以下代碼以及事務管理器代碼綜合得出.

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
    try {
        return doGetConnection(dataSource);
    }
    catch (SQLException ex) {
        throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
    }
}
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
    Assert.notNull(dataSource, "No DataSource specified");
    // 從當前線程獲取綁定的ConnectionHolder
    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager
        .getResource(dataSource);
	// 如果不為空,並且內部持有連接,直接返回此連接
    // 首先開啟事務綁定的conHolder是肯定持有連接的,那么什么時候conHolder.hasConnection() = false
    // 答案要繼續往下找,才能明白。
    // 這里直接解釋下: 此方法在一個空事務中會綁定一個connectionHolder,
    // 同時注冊了一個ConnectionSynchronization事件,如果這個空事務被掛起,就會觸發這個
    // 事件的suspend鈎子,這個鈎子除了會解綁這個connectionHolder外,還會判斷
    // connectionHolder持有的連接是否還被外部程序使用,如果不再使用了,會提前釋放掉。
    // 因此如果后面這個事務被恢復了,那么能從數據源中直接獲取一個新的連接
    if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
        // 將引用計數加1,代表獲取這個conHolder的次數
        conHolder.requested();
        if (!conHolder.hasConnection()) {
            logger.debug("Fetching resumed JDBC Connection from DataSource");
            conHolder.setConnection(dataSource.getConnection());
        }
        return conHolder.getConnection();
    }

    logger.debug("Fetching JDBC Connection from DataSource");
    // 直接從給定的數據源獲取連接
    Connection con = dataSource.getConnection();
	// 如果事務同步是開啟的,從前面事務源碼分析,可以知道如果開啟了一個空事務(非事務方式運行),
    // 這個方法的值也會返回true.
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        logger.debug("Registering transaction synchronization for JDBC Connection");
        // 使用這個連接創建一個ConnectionHolder
        ConnectionHolder holderToUse = conHolder;
        if (holderToUse == null) {
            holderToUse = new ConnectionHolder(con);
        }
        else {
            holderToUse.setConnection(con);
        }
        holderToUse.requested();
        // 注冊一個TransactionSynchronization
        // 這里出現一個新類ConnectionSynchronization,看下面解析。
        // 當事務運行整個期間會觸發這個類實現的鈎子方法
        TransactionSynchronizationManager.registerSynchronization(
            new ConnectionSynchronization(holderToUse, dataSource));
        holderToUse.setSynchronizedWithTransaction(true);
        if (holderToUse != conHolder) {
            // 將這個連接綁定到當前線程
            TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
        }
    }
    return con;
}
// 主要關注這個實現的鈎子方法都做了啥
// 首先明確一個觀點,由getConnection方法可知,此類被注冊前,拿到的連接都是數據源中新鮮的連接
// 也就意味者這個連接是非事務的。
private static class ConnectionSynchronization extends TransactionSynchronizationAdapter {

    private final ConnectionHolder connectionHolder;

    private final DataSource dataSource;

    private int order;

    private boolean holderActive = true;

    public ConnectionSynchronization(ConnectionHolder connectionHolder, 
                                     DataSource dataSource) {
        this.connectionHolder = connectionHolder;
        this.dataSource = dataSource;
        this.order = getConnectionSynchronizationOrder(dataSource);
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    // 當存在一個空事務的時候,getConnection()方法會從數據源中拿到一個資源並綁定到當前線程。
    // 因此當這個空事務被掛起的時候,需要解綁
    // 當外部不存在真實事務時,新建一個新的真實事務,會掛起外部空事務
    // 具體可看AbstractPlatformTransactionManager中getTransaction方法的第二個大分支
    @Override
    public void suspend() {
        if (this.holderActive) {
            // 解綁綁定的連接
            TransactionSynchronizationManager.unbindResource(this.dataSource);
            // 如果這個連接沒有再被引用了,也就是說不再使用了,將這個連接提前釋放。
            // 這段代碼也解決了getConnection方法提的疑問。
            if (this.connectionHolder.hasConnection() && 
                !this.connectionHolder.isOpen()) {
                // Release Connection on suspend if the application doesn't keep
                // a handle to it anymore. We will fetch a fresh Connection if the
                // application accesses the ConnectionHolder again after resume,
                // assuming that it will participate in the same transaction.
                releaseConnection(this.connectionHolder.
                                  getConnection(), this.dataSource);
                this.connectionHolder.setConnection(null);
            }
        }
    }

    @Override
    public void resume() {
        // 恢復,與suspend對應
        if (this.holderActive) {
            TransactionSynchronizationManager.bindResource(
                this.dataSource, this.connectionHolder);
        }
    }

    @Override
    public void beforeCompletion() {
        // 如果外部程序不再使用此連接,解綁connectionHolder,並且釋放
        // 因為這個連接是非事務的,Spring在調用rollback或者commit不會有實際的事務操作
        // 因此如果不再使用可以提前釋放掉
        if (!this.connectionHolder.isOpen()) {
            TransactionSynchronizationManager.unbindResource(this.dataSource);
            this.holderActive = false;
            if (this.connectionHolder.hasConnection()) {
                releaseConnection(this.connectionHolder
                                  .getConnection(), this.dataSource);
            }
        }
    }

    @Override
    public void afterCompletion(int status) {
        // 事務完成后,還沒有在beforeCompletion鈎子方法中解綁釋放掉
        // 那么解綁釋放掉
        if (this.holderActive) {
            // The thread-bound ConnectionHolder might not be available anymore,
            // since afterCompletion might get called from a different thread.
            // 這段注釋沒明白,會什么afterCompletion鈎子會被多個線程調用
            // 而beforeCompletion卻不會呢? 不應該都是在以一個線程內執行嗎?
            // 現在知道的一點原因是在JTA事務中會發生多個線程調用的問題
            // DataSourceTransactionManager這個管理器不會發生上述情況
            TransactionSynchronizationManager.unbindResourceIfPossible(this.dataSource);
            this.holderActive = false;
            if (this.connectionHolder.hasConnection()) {
                releaseConnection(this.connectionHolder.
                                  getConnection(), this.dataSource);
                // Reset the ConnectionHolder: It might remain bound to the thread.
                this.connectionHolder.setConnection(null);
            }
        }
        this.connectionHolder.reset();
    }
}

4.2 釋放連接(releaseConnection)

public static void releaseConnection(Connection con, DataSource dataSource) {
    try {
        doReleaseConnection(con, dataSource);
    }
    catch (SQLException ex) {
        logger.debug("Could not close JDBC Connection", ex);
    }
    catch (Throwable ex) {
        logger.debug("Unexpected exception on closing JDBC Connection", ex);
    }
}

public static void doReleaseConnection(Connection con, 
                                       DataSource dataSource) throws SQLException {
    if (con == null) {
        return;
    }
    if (dataSource != null) {
        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationM
            anager.getResource(dataSource);
        // 此連接受Spring事務管理,可能是一個空事務,也可能是一個真實事務
        if (conHolder != null && connectionEquals(conHolder, con)) {
            // 將此連接引用數減一
            conHolder.released();
            return;
        }
    }
    logger.debug("Returning JDBC Connection to DataSource");
    // 不受Spring事務管理的連接,釋放掉,歸還給數據源。
    doCloseConnection(con, dataSource);
}

可以看到如果我們直接調用這個方法來釋放連接時,如果這個連接由Spring事務管理,並不會真的將這個連接釋放掉,只是將這個連接的引用次數減一,要想真的釋放掉這個連接,需要先解綁這個擁有此連接的connectinHolder,然后再調用次方法才能釋放掉。觀察事務管理器的釋放操作,也可以看到事務真正提交或者回滾后要釋放掉這個連接時,會先解綁,然后再調用此方法釋放連接。

4.3 isConnectionTransactional

此方法用來判斷給定的連接是不是由Spring事務管理器綁定在當前線程,包括真實事務以及空事務。

  • 真實事務由事務管理的getTransaction方法綁定,准確點我們知道其實是doBegin方法。

  • 空事務是由DataSourceUtils.getConnection()綁定。下面用一段偽代碼描述

    // 已非事務方式運行(開啟一個空事務)
    TransactionDefinition definition = new DefaultTransactionDefinition(
    				TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
    TransactionStatus status = transactionManager.getTransaction(definition);
    try {
        // 此種情況會綁定連接到當前線程
        Connection conn = DataSourceUtils.getConnection(dataSource);
        // use conn doSomething
        transactionManager.commit(status);
    } catch(RuntimeExecption e) {
         transactionManager.rollback(status);
    }
      
    

代碼分析:

/** 
 * 代碼很簡單,理解了上面兩點,這段代碼一看就懂
 * 如果要判斷此連接是Spring事務管理,並且是真實事務
 * 可以這樣簡單判斷
 * return conHolder != null 
 *        && conHolder.getConnection() == con 
 *        && conHolder.isTransactionActive();
 */
public static boolean isConnectionTransactional(Connection con, DataSource dataSource) {
    if (dataSource == null) {
        return false;
    }
    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    return (conHolder != null && connectionEquals(conHolder, con));
}

5. 總結

本文主要分析了工具類中最重要也是最常用的幾個方法,其它方法非常簡單,也就沒啥好說的了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM