深入理解 Spring 之 SpringBoot 事務原理

前言

今天是平安夜,先祝大家平安夜快樂。

我們之前的數十篇文章分析了 Spring 和 Mybatis 的原理,基本上從源碼層面都了解了他們的基本原理,那么。在我們日常使用這些框架的時候,還有哪些疑問呢?就樓主而言,樓主已經明白了 IOC ,AOP 的原理,也明白了 Mybatis 的原理,也明白了 Spring 和 Mybatis 是如何整合的。但是,我們漏掉了 JavaEE 中一個非常重要的特性:事務。事務是 Java 程序員開發程序時不可避免的問題。我們就不討論 ACID 的事務特性,樓主這里假定大家都已經了了解了事務的原理。如果還不了解,可以先去谷歌看看。那么,我們今天的任務是剖析源碼,看看Spring 是怎么運行事務的,并且是基于當前最流行的SpringBoot。還有,我們之前剖析Mybatis 的時候,也知道,Mybatis 也有事務,那么,他倆融合之后,事務是交給誰的?又是怎么切換的?今天這幾個問題,我們都要從源碼中找到答案。

1. Spring 的事務如何運行?

如果各位使用過SpringBoot ,那么就一定知道如何在Spring中使用注解,比如在一個類或者一個方法上使用 @Transactional 注解,在一個配置類上加入一個 @EnableTransactionManagement 注解代表啟動事務。而這個配置類需要實現 TransactionManagementConfigurer 事務管理器配置接口。并實現 annotationDrivenTransactionManager 方法返回一個包含了 配置好數據源的 DataSourceTransactionManager 事務對象。這樣就完成了事務配置,就可以在Spring使用事務的回滾或者提交功能了。

這個 saveList 方法就在Spring事務的控制之下,如果發生了異常,就會回滾事務。如果各位知道更多的Spring的事務特性,可以在注解中配置,比如什么異常才能回滾,比如超時時間,比如隔離級別,比如事務的傳播。就更有利于理解今天的文章了。

我們基于一個 Junit 測試用例,來看看Spring的事務時如何運行的。

在測試用例中執行該方法,參數時一個空的List,這個Sql的運行肯定是失敗的。我們主要看看他的運行過程。我們講斷點打在該方法上。斷點進入該方法。

注意,dataCollectionShareService 對象已經被 Cglib 代理了,那么他肯定會走 DynamicAdvisedInterceptor 的 intercept 方法,我們斷點進入該方法查看,這個方法我們已經很屬性了,該方法中,最重要的事情就是執行通知器或者攔截器的方法,那么,該代理有通知器嗎?

有一個通知器。是什么呢?

一個事務攔截器,也就是說,如果通知器鏈不為空,就會依次執行通知器鏈的方法。那么 TransactionInterceptor 到底是什么呢?

該類實現了通知器接口,也實現類 MethodInterceptor 接口,并實現了該接口的 invoke 方法,在 DynamicAdvisedInterceptor 的 intercept 方法中,最終會調用每個 MethodInterceptor 的 invoke 方法,那么,TransactionInterceptor 的 invoke 方法是如何實現的呢?

invoke 方法中會調用自身的 invokeWithinTransaction 方法,看名字,該方法和事務相關。該方法參數是由目標方法,目標類,一個回調對象構成。 那么我們就進入該方法查看,該方法很長:

    /**
     * General delegate for around-advice-based subclasses, delegating to several other template
     * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager}
     * as well as regular {@link PlatformTransactionManager} implementations.
     * @param method the Method being invoked
     * @param targetClass the target class that we're invoking the method on
     * @param invocation the callback to use for proceeding with the target invocation
     * @return the return value of the method, if any
     * @throws Throwable propagated from the target invocation
     */
    protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
            throws Throwable {

        // If the transaction attribute is null, the method is non-transactional.
        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)) {
            // Standard transaction demarcation with getTransaction and commit/rollback calls.
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            Object retVal = null;
            try {
                // This is an around advice: Invoke the next interceptor in the chain.
                // This will normally result in a target object being invoked.
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                // target invocation exception
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                cleanupTransactionInfo(txInfo);
            }
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }

        else {
            // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
            try {
                Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
                        new TransactionCallback<Object>() {
                            @Override
                            public Object doInTransaction(TransactionStatus status) {
                                TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                                try {
                                    return invocation.proceedWithInvocation();
                                }
                                catch (Throwable ex) {
                                    if (txAttr.rollbackOn(ex)) {
                                        // A RuntimeException: will lead to a rollback.
                                        if (ex instanceof RuntimeException) {
                                            throw (RuntimeException) ex;
                                        }
                                        else {
                                            throw new ThrowableHolderException(ex);
                                        }
                                    }
                                    else {
                                        // A normal return value: will lead to a commit.
                                        return new ThrowableHolder(ex);
                                    }
                                }
                                finally {
                                    cleanupTransactionInfo(txInfo);
                                }
                            }
                        });

                // Check result: It might indicate a Throwable to rethrow.
                if (result instanceof ThrowableHolder) {
                    throw ((ThrowableHolder) result).getThrowable();
                }
                else {
                    return result;
                }
            }
            catch (ThrowableHolderException ex) {
                throw ex.getCause();
            }
        }
    }

該方法主要邏輯:

  1. 獲取事務屬性,根據事務屬性,獲取事務管理器。
  2. 判斷屬性是否空,或者事務管理器是否不是 CallbackPreferringPlatformTransactionManager 類型,如果是該類型,則會執行事務管理器的 execute 方法。
  3. 生成一個封裝了事務管理器,事務屬性,方法簽名字符串,事務狀態對象 的 TransactionInfo 事務信息對象。該對象會在事務回滾或者失敗時起作用。
  4. 調用目標對象方法或者是下一個過濾器的方法。
  5. 如果方法由異常則執行 completeTransactionAfterThrowing 方法,調用事務管理器的回滾方法。如果沒有異常,調用 commitTransactionAfterReturning 提交方法。最后返回返回值。

可以說,該方法就是Spring 事務的核心調用,根據目標方法是否有異常進行事務的回滾。

那么,我們需要一行一行的看看該方法實現。

首先看事務的屬性。

2. TransactionAttribute 事務屬性

invokeWithinTransaction 方法中調用了 自身的 getTransactionAttributeSource 方法返回一個TransactionAttributeSource 對象,并調用該對象的 getTransactionAttribute 方法,參數是目標方法和目標類對象。首先看 getTransactionAttributeSource 方法,該方法直接返回了抽象類 TransactionAspectSupport 中定義的 TransactionAttributeSource 屬性。該屬性的是什么時候生成的我們稍后再說。我們debug 后返回的是 TransactionAttributeSource 接口的實現類 AnnotationTransactionAttributeSource ,看名字,注解事務屬性資源,名字起的好很重要啊。我們進入該類查看。

這是該類的繼承機構圖。我們重點還是關注該類的 getTransactionAttribute 方法,該方法有抽象類 AbstractFallbackTransactionAttributeSource 也就是 AnnotationTransactionAttributeSource 的父類完成。我們看看該方法。

該方法大部分都是緩存判斷,最重要的一行代碼樓主已紅框標出。computeTransactionAttribute 方法,計算事務屬性。進入該方法查看:

該方法是返回事務屬性的核心方法,首先,根據 class 和 method 對象,生成一個完整的method 對象,然后調用 findTransactionAttribute 方法,參數就是該 method 對象,findTransactionAttribute 方法是抽象方法,由子類實現,可見 computeTransactionAttribute 是個模板方法模式。那么我們就看看他的子類 AnnotationTransactionAttributeSource 是如何實現的。該方法調用了自身的 determineTransactionAttribute 方法。該方法實現入下:

該方法會判斷該 Method 對象是否含有注解。并循環 AnnotationTransactionAttributeSource 對象的 annotationParsers 注解解析器集合,對該方法進行解析。如果解析成功,則返回該注解元素。我想我們也已經猜到了,這個注解解析器解析的就是 @Transactional 注解。

3. @Transactional 注解解析器 SpringTransactionAnnotationParser

我們說AnnotationTransactionAttributeSource 對象中又多個解析器。那么這些解析器是什么時候生成的呢?構造方法中生成的。

該構造方法由一個布爾屬性,然后創建一個鏈表,也創建一個 SpringTransactionAnnotationParser 對象添加進鏈表中。這樣就完成了解析器的創建。構造方法什么時候調用的呢?我們稍后再講。

我們看看注解解析器是怎么解析方法對象的。

首先根據指定的 Transactional 注解和給定的方法,調用工具方法 getMergedAnnotationAttributes ,獲取方法上的注解屬性。然后調用重載方法 parseTransactionAnnotation 。

可以看到,該方法首先創建了一個 RuleBasedTransactionAttribute 對象,然后一個個解析注解中的元素,并將這些元素設置到 RuleBasedTransactionAttribute 對象中,注意,其中有個 RollbackRuleAttribute 的集合,存儲著該注解屬性的回滾相關的屬性。最后添加到 RuleBasedTransactionAttribute 的RollbackRules 集合中。

到這里,就完成了解析器的解析。返回了一個 RuleBasedTransactionAttribute 對象。

回到 攔截器的 invokeWithinTransaction 方法中,此時已經獲取了 屬性對象。根據方法,也就是說,如果返回值是null,說明該方法沒有事務注解,在 getTransactionAttribute 方法中,也會將該方法作為key ,NULL_TRANSACTION_ATTRIBUTE 作為 value,放入緩存,如果不為null,那么就將 TransactionAttribute 作為 value 放入緩存。

有了事務屬性,再獲取事務管理器。也就是 determineTransactionManager 方法。

4. 事務管理器。

我們注意到,調用了自身的 determineTransactionManager 方法,返回了一個 PlatformTransactionManager 事務管理器。這個事務管理器就是我們在我們的配置類中寫的:

那么這個事務管理器是什么呢?事務管理器就是真正執行事務回滾或提交的執行單位,我們看看該類:

繼承圖:


結構圖:


紅框標注的方法就是執行正在事務邏輯的方法,其中又封裝了數據源,也就是 JDBC 的 Connection 。比如 doCommit 方法:

我們看看determineTransactionManager 是如何獲取事務管理器的。

該方法步驟入下:

  1. 如果事務屬性為null 或者 容器工廠為null,則返會自身的 transactionManager 事務管理器。
  2. 如果都不為null,則獲取事務屬性的限定符號,根據限定符從容器中獲取 事務管理器。
  3. 如果沒有限定符,則根據事務管理器的BeanName從容器中獲取。
  4. 如果都沒有,則獲取自身的事務管理器,如果自身還沒有,則從緩存中取出默認的。如果默認的還沒有,則從容器中獲取PlatformTransactionManager 類型的事務管理器,最后返回。

這里重點是自身的事務管理器從何而來?我們先按下不表。

到這里,我們已經有了事務管理器。就需要執行 invokeWithinTransaction 下面的邏輯了。回到 invokeWithinTransaction 方法,我們的返回值肯定滿足第一個if 條件,因為我們的事務管理器不是 CallbackPreferringPlatformTransactionManager 類型的。進入if 塊。

首先創建一個事務信息對象。該類是什么呢?

屬性:


構造方法:


該類包含了一個 事務管理器,事務屬性,事務方法字符串。

接著執行回調類InvocationCallback 的 proceedWithInvocation 方法,該方法會執行下一個通知器的攔截方法(如果有的話),最后執行目標方法,這里,目標方法被 try 住了,如果發生異常,則執行completeTransactionAfterThrowing 方法,并拋出異常,在 finally 塊中執行清理工作。如果成功執行,則執行
commitTransactionAfterReturning 方法。最后返回目標方法返回值。

我們重點看看 completeTransactionAfterThrowing 方法和 commitTransactionAfterReturning 方法。

5. TransactionInterceptor 的 completeTransactionAfterThrowing 方法(事務如何回滾)。

該方法主要內容在紅框中,首先判斷該事務對象是否和該異常匹配,如果匹配,則回滾,否則,則提交。那么,是否匹配的邏輯是怎么樣的呢?我們的事務屬性是什么類型的?RuleBasedTransactionAttribute ,就是我們剛剛創建解析注解后創建的。那么我就看看該類的 rollbackOn 方法:

首先,循環解析注解時添加進集合的回滾元素。并遞歸調用RollbackRuleAttribute 的 getDepth 方法,如果這個異常的名字和注解中的異常名字匹配,則返回該異常的回滾類型。最后判斷,如果沒有匹配到,則調用父類的 rollbackOn 方法,如果匹配到了,并且該屬性類型不是 NoRollbackRuleAttribute 類型,返回true。表示匹配到了,可以回滾。那么父類的 rollbackOn 方法肯定就是默認的回滾方法了。

這是父類的 rollbackOn 方法:

該方法判斷,該異常如果是 RuntimeException 類型異常或者 是 Error 類型的,就回滾。這就是默認的回滾策略。

那么我們的方法肯定是匹配的 RuntimeException 異常,就會執行下面的方法。

可以看到,這行代碼就是執行了我們的事務管理器的 rollback 方法,并且攜帶了事務狀態對象。該方法實現在抽象類 AbstractPlatformTransactionManager 中,調用了自身的 processRollback 方法做真正的實現。

該方法首先切換事務狀態,其實就是關閉SqlSession。

然后調用 doRollback 方法。

首先,從狀態對象中獲取數據庫連接持有對象,然后獲取數據庫連接,調用 Connection 的 rollback 方法,也就是我們學習JDBC 時使用的方法。最后修改事務的狀態。

到這里,事務的回滾就結束了。

那么,事務時如何提交的呢?

6. TransactionInterceptor 的 commitTransactionAfterReturning 方法(事務如何提交)。

該方法簡單的調用了事務管理器的 commit 方法。

AbstractPlatformTransactionManager 的 commit 方法。

首先判斷了事務的狀態,如果狀態不匹配,則調用回滾方法。如果狀態正常,執行 processCommit 方法。該方法很長,樓主只截取其中一段:

首先,commit 之前做一些狀態切換工作。最重要的是執行 doCommit 方法,如果異常了,則回滾。那么 DataSourceTransactionManager 的 doCommit 是如何執行的呢?

可以看到,底層也是調用 JDBC 的 Connection 的 commit 方法。

到這里,我們就完成了數據庫的提交。

7. 事務運行之前做了哪些工作?

從前面的分析,我們已經知道了事務是如何運行的,如何回滾的,又是如何提交的。在這是交互型的框架里,事務系統肯定做了很多的準備工作,同時,我們留下了很多的疑問,比如事務管理器從何而來? TransactionAttributeSource 屬性何時生成?AnnotationTransactionAttributeSource 構造什么時候調用?

我們一個個的來解釋。

在Spring 中,有一個現成的類,ProxyTransactionManagementConfiguration,我們看看該類:

看到這個類,應該可以解開我們的疑惑,這個類標注了配置注解,會在IOC的時候實例化該類,而該類中產生了幾個Bean,比如事務攔截器 TransactionInterceptor,創建了 AnnotationTransactionAttributeSource 對象,并向事務攔截器添加了事務管理器。最后,將事務攔截器封裝成通知器。那么,剩下最后一個問題就是,事務管理器從何而來?答案是他的父類 AbstractTransactionManagementConfiguration :

該類也是個配置類,自動注入了 TransactionManagementConfigurer 的配置集合,而并且尋找了配置 EnableTransactionManagement 注解的類,而我們在我們的項目中就是按照這個標準來實現的:

我們關聯這兩個類就能一目了然,Spring在啟動的時候,會加載這兩個配置類,在對 AbstractTransactionManagementConfiguration 的 setConfigurers 方法進行注入的時候,會從容器中找到對應類型的配置,并調用配置類的 annotationDrivenTransactionManager 方法,也就是我們實現的方法,獲取到我們創建的 DataSourceTransactionManager 類。這樣,我們的事務攔截器相關的類就完成了在Spring中的依賴關系。

但是,這個時候Spring中的事務運行還沒有搭建完成。比如什么時候創建類的代理?根據什么創建代理,因為我們知道,Spring 中的事務就是使用AOP來完成的,必須使用動態代理或者 Cglib 代理來對目標方法進行攔截。

這就要復習我們之前的Spring IOC 的啟動過程了。Spring 在創建bean的時候,會對每個Bean 的所有方法進行遍歷,如果該方法匹配系統中任何一個攔截器的切點,就創建一個該Bean的代理對象。并且會將對應的通知器放入到代理類中。以便在執行代理方法的時候進行攔截。

具體代碼步驟樓主貼一下:

  1. 在對bean 進行初始化的時候會執行 AutowireCapableBeanFactory 接口的 applyBeanPostProcessorsAfterInitialization 的方法,其中會遍歷容器中所有的bean后置處理器,后置處理器會調用 postProcessAfterInitialization 方法對bean進行處理。
  1. 在處理過程中,對bean 進行包裝,也就是代理的創建,調用 getAdvicesAndAdvisorsForBean 方法,該方法會根據bean的信息獲取到對應的攔截器并創建代理,創建代理的過程我們之前已經分析過了,不再贅述。
  1. 尋找匹配攔截器過程:首先找到所有的攔截器,然后,根據bean的信息進行匹配。
  1. 匹配的過程就是,找到目標類的所有方法,遍歷,并調用攔截器的方法匹配器對每個方法進行匹配。方法匹配器就是事務攔截器中的 BeanFactoryTransactionAttributeSourceAdvisor 類,該類封裝了 AnnotationTransactionAttributeSource 用于匹配事務注解的匹配器。
  1. 最終調用方法匹配器中封裝的注解解析器解析方法,判斷方法是否含有事務注解從而決定是否生成代理:

到這里,就完成了所有事務代理對象的創建。

項目中的每個Bean都有了代理對象,在執行目標方法的時候,代理類會查看目標方法是否匹配代理中攔截器的方法匹配器中定義的切點。如果匹配,則執行攔截器的攔截方法,否則,直接執行目標方法。這就是含有事務注解和不含有事務注解方法的執行區別。

到這里,我們還剩下最后一個問題,我們知道,在分析mybatis 的時候,mybatis 也有自己的事務管理器,那么他們融合之后,他們的事務管理權在誰的手上,又是根據什么切換的呢?

8. mybatis 和 Spring 的事務管理權力之爭

我們之前說過,在Spring中,mybatis 有 SqlSessionTemplate 代理執行,其實現類動態代理的 InvocationHandler 方法,那么最重要的方法就是 invoke 方法,其實這個方法我們已經看過了,今天再看一遍:

我們今天重點關注是否提交(報錯肯定回滾),其中紅框標出來的 if 判斷,就是判斷這個事務到底是Spring 來提交,還是 mybatis 來提交,那么我們看看這個方法 isSqlSessionTransactional :

該方法從Spring 的容器中取出持有 SqlSession 的 持有類,判斷Spirng 持有的 SqlSession 和 Mybatis 持有的是否是同一個,如果是,則交給Spring,否則,Mybatis 自己處理。可以說很合理。

總結

今天的這篇文章可以說非常的長,我們分析了 SpringBoot 的事務運行過程,事務環境的搭建過程,mybatis 的事務和 Spring 事務如何協作。知道了整個事務其實是建立在AOP的基礎之上,其核心類就是 TransactionInterceptor,該類就是 invokeWithinTransaction 方法是就事務處理的核心方法,其中封裝了我們創建的 DataSourceTransactionManager 對象,該對象就是執行回滾或者提交的執行單位 其實,TransactionInterceptor 和我們平時標注 @Aspect 注解的類的作用相同,就是攔截指定的方法,而在
TransactionInterceptor 中是通過是否標有事務注解來決定的。如果一個類中任意方法含有事務注解,那么這個方法就會被代理。而Mybatis 的事務和Spring 的事務協作則根據他們的SqlSession 是否是同一個SqlSession 來決定的,如果是同一個,則交給Spring,如果不是,Mybatis 則自己處理。

通過閱讀源碼,我們已經弄清楚了SpirngBoot 整個事務的運行過程。實際上,Spring 的其他版本也大同小異。底層都是 TransactionInterceptor ,只不過入口不一樣。我相信,在以后的工作中,如果遇到了Spring事務相關的問題,再也不會感到無助了,因為知道了原理,可以深入到源碼中查看。

到這里,樓主的 Spring ,mybatis ,Tomcat 的源碼閱讀之路暫時就告一段落了。源碼只要領會精華即可。還有其他的知識需要花費更多的時間學習。比如并發,JVM.

good luck!!!!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,890評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,947評論 6 342
  • 1. 簡介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優秀的...
    笨鳥慢飛閱讀 5,590評論 0 4
  • 今天的圖,男,34歲!主題:森林深處 第100幅的分析,本來想留給我老公的,怎奈他不愿意向我敞開自己...此處應該...
    Nina張閱讀 1,722評論 0 1
  • 5月29日晨讀感悟 ---生活中處處都是談判 想到談判,就像港劇《談判專家》里面的場景,這些西裝筆挺的談判專家,個...
    shmily由由and花花閱讀 170評論 0 0