Spring Transaction + MyBatis SqlSession事務管理機制研究學習

 原文地址:Spring Transaction + MyBatis SqlSession事務管理機制研究學習

    線上的系統中,使用的是Spring+Mybatis+Mysql搭建的框架,由於客戶需要,最近一直在對性能提升部分進行考慮,主要是涉及Mysql的一些重要參數的配置學習,以及Spring事務管理機制的學習,因爲通過觀察服務器日誌,發現在這兩部分的時候耗時比較嚴重,特別是進行mysql事務提交的時候,項目源碼中使用了Spring的聲明式事務,即通過@Transactional註解來控制事務的開啓與提交,這兩天看了一些關於Spring Transaction事務的一些文章,也debug了源碼,總算有點心得和疑問,這裏簡單的整理一下。


    在Spring的配置文件中,我們使用了"org.springframework.jdbc.datasource.DataSourceTransactionManager"對事務進行管理,翻開DataSourceTransactionManager的源碼,我們看到DataSourceTransactionManager繼承了AbstractPlatformTransactionManager(抽象的事務管理器),DataSourceTransactionManager重寫了其中的一些方法,具體每個方法的作用,限於篇幅,本文不再贅述,這裏《Spring技術內幕》學習筆記16——Spring具體事務處理器的實現有詳細的介紹。


     在現有的項目中,我們在public方法上面使用了@Transactional註解,當有線程調用此方法時,Spring會首先掃描到@Transactional註解,進入DataSourceTransactionManager繼承自AbstractPlatformTransactionManager的getTransaction()方法,在getTransaction()方法內部,會調用doGetTransaction()方法,@Transactional的註解中,存在一個事務傳播行爲的概念,即propagation參數,默認等於PROPAGATION_REQUIRED,表示如果當前沒有事務,就新建一個事務,如果存在一個事務,方法塊將使用這個事務,具體其他參數的意義請看下圖:

        在getTransaction方法中,DataSourceTransactionManager重寫了isExistingTransaction()方法,用於判斷當前是否存在事務,以下是其的源碼:

?
1
2
3
4
5
6
    @Override
    protected boolean isExistingTransaction(Object transaction) {
        logger.debug(Thread.currentThread().getName() + ">>>" "DataSourceTransactionManager.isExistingTransaction()");
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        return (txObject.getConnectionHolder() != null && txObject.getConnectionHolder().isTransactionActive());
    }

    但是,這幾天我對源碼進行調試的過程中,發現多線程併發的時候,isExistingTransaction方法總是返回的false,即ConnectionHolder總是爲空,這是遇到的第一個疑問點,目前還沒有弄清楚。由於源碼判斷當前不存在事務,所以總是會Creating new transaction,即新建一個事務。新建事務之後,會執行重寫的doBegin()方法,在doBegin方法中,首先通過下面的代碼判斷了ConnectionHolder是否爲空,如下:

?
1
2
3
4
5
6
7
8
9
            if (txObject.getConnectionHolder() == null
                    || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                Connection newCon = this.dataSource.getConnection();
                if (logger.isDebugEnabled()) {
                    logger.debug(Thread.currentThread().getName() + ">>>" "Acquired Connection [" + newCon
                            "] for JDBC transaction");
                }
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }

    這裏會從當前配置的數據源中獲取一個連接,然後設置相應的ConnectionHolder,接下來是關鍵的一步,也是存在的第二個問題點,拿到connection後,會首先判斷connection的autoCommit屬性是否爲true,之前工作中在使用原始JDBC的時候,當進行事務的控制時,我們總是會首先設置autoCommit爲false,禁止事務自動提交,然後commit提交事務,最後設置autoCommit爲true。Spring Transaction也是這樣進行管理的,但是問題來了, 先看源碼:

?
1
2
3
4
5
6
7
8
9
10
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                if (logger.isDebugEnabled()) {
                    logger.debug(Thread.currentThread().getName() + ">>>" "Switching JDBC Connection [" + con
                            "] to manual commit");
                }
                con.setAutoCommit(false);
                logger.debug(Thread.currentThread().getName() + ">>>" "Set Transaction AutoCommit False [" + con
                        "]");
            }

  這裏有個Swithching JDBC Connection的操作,就是爲了設置autoCommit爲false,但是在這幾天進行多線程併發測試的時候,發現這一部分的代碼耗時非常嚴重,這是目前還不清楚的第二點。


    接下來,開始涉及MyBatis SqlSession部分的一些機制,關於MyBatis sqlSession的一點整理,SqlSession主要是MyBatis定義的用於進行持久化操作的對象,對connection進行了包裝。在上面Spring Transaction的機制中,我們獲取到了connection,之後會首先調用SqlSessionUtils裏面的getSession方法,判斷當前的sqlSessionHolder以及是否存在事務鎖定。


    如果不存在sessionHolder或resources未被事務鎖定,就會Creating a new SqlSession,然後爲sqlSession註冊事務資源,即Registering transaction synchronization for SqlSession。之後把connection交給Spring管理;如果存在可用的sessionHolder並且被事務鎖定,就會從當前的事務中拿到SqlSession,即Fetched SqlSession from current transaction。當代碼中涉及數據庫的操作時,就可以從數據源中獲取到相應的connection,即Using Connection,這時候就有了第三個問題,在進行debug源碼測試的時候,發現using connection這一步的耗時也比較嚴重。


    在使用完連接後,SqlSessionUtils會釋放事務的SqlSession,即Releasing transactional SqlSession。接下來,就可以準備執行事務的提交了,即Initiating transaction commit,這裏會調用DataSourceTransactionManager中重寫的doCommit()方法,在其中進行事務的提交操作,即Committing JDBC transaction on Connection,爲當前的連接提交事務。這裏又有了第四個問題,進行事務提交的操作,在進行多線程併發測試的時候,發現耗時非常嚴重。也嘗試過修改連接池或者mysql的配置,問題總是得不到解決。


    在事務提交之後,又回到SqlSessionUtils執行其中的afterCompletion方法,進行MyBatis SqlSession層面的處理。SqlSessionUtils會首先Transaction synchronization committing SqlSession,提交SqlSession,然後關閉SqlSession,即Transaction synchronization closing SqlSession,在MyBatis層面處理完成後,會再次回到DataSourceTransactionManager,執行其中的doCleanupAfterCompletion方法,釋放一些資源,包括: Releasing JDBC Connection 釋放JDBC連接,Returning JDBC Connection to DataSource 將連接放回到數據源。


    至此,對於Spring Transaction + MyBatis SqlSession事務管理機制,已經做了大致的研究學習,閱讀了其中涉及的源碼,以及參閱了一些網上的博客,有了一些自己的認識,很近自己的理解整理出了一個簡單的時序圖,其中也有一些疑問,最後做一下記錄。


    主要是包括四個疑問點:

  • 問題1: 在現有框架下isExistingTransaction返回false,即ConnectionHolder爲空

  • 問題2: 切換JDBC connection屬性,主要是setAutoCommit爲false,禁止事務自動提交,耗時不穩定,有時很慢

  • 問題3: 使用數據源中的connection時,connection獲取耗時有時很慢

  • 問題4: Spring Transaction再進行事務提交時commit耗時嚴重


    這裏根據自己的理解整理出了一個簡單的時序圖,共享給大家,其中一定涉及一些不合理甚至理解錯誤的地方,希望大家不吝賜教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章