深入理解 Spring 之 SpringBoot 事務(wù)原理

前言

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

我們之前的數(shù)十篇文章分析了 Spring 和 Mybatis 的原理,基本上從源碼層面都了解了他們的基本原理抄罕,那么毫痕。在我們?nèi)粘J褂眠@些框架的時(shí)候征峦,還有哪些疑問呢?就樓主而言消请,樓主已經(jīng)明白了 IOC 栏笆,AOP 的原理,也明白了 Mybatis 的原理臊泰,也明白了 Spring 和 Mybatis 是如何整合的蛉加。但是,我們漏掉了 JavaEE 中一個(gè)非常重要的特性:事務(wù)因宇。事務(wù)是 Java 程序員開發(fā)程序時(shí)不可避免的問題七婴。我們就不討論 ACID 的事務(wù)特性祟偷,樓主這里假定大家都已經(jīng)了了解了事務(wù)的原理察滑。如果還不了解,可以先去谷歌看看修肠。那么贺辰,我們今天的任務(wù)是剖析源碼,看看Spring 是怎么運(yùn)行事務(wù)的嵌施,并且是基于當(dāng)前最流行的SpringBoot饲化。還有,我們之前剖析Mybatis 的時(shí)候吗伤,也知道吃靠,Mybatis 也有事務(wù),那么足淆,他倆融合之后巢块,事務(wù)是交給誰的礁阁?又是怎么切換的?今天這幾個(gè)問題族奢,我們都要從源碼中找到答案姥闭。

1. Spring 的事務(wù)如何運(yùn)行?

如果各位使用過SpringBoot 越走,那么就一定知道如何在Spring中使用注解棚品,比如在一個(gè)類或者一個(gè)方法上使用 @Transactional 注解,在一個(gè)配置類上加入一個(gè) @EnableTransactionManagement 注解代表啟動(dòng)事務(wù)廊敌。而這個(gè)配置類需要實(shí)現(xiàn) TransactionManagementConfigurer 事務(wù)管理器配置接口铜跑。并實(shí)現(xiàn) annotationDrivenTransactionManager 方法返回一個(gè)包含了 配置好數(shù)據(jù)源的 DataSourceTransactionManager 事務(wù)對(duì)象。這樣就完成了事務(wù)配置庭敦,就可以在Spring使用事務(wù)的回滾或者提交功能了疼进。

這個(gè) saveList 方法就在Spring事務(wù)的控制之下,如果發(fā)生了異常秧廉,就會(huì)回滾事務(wù)伞广。如果各位知道更多的Spring的事務(wù)特性,可以在注解中配置疼电,比如什么異常才能回滾嚼锄,比如超時(shí)時(shí)間,比如隔離級(jí)別蔽豺,比如事務(wù)的傳播区丑。就更有利于理解今天的文章了。

我們基于一個(gè) Junit 測(cè)試用例修陡,來看看Spring的事務(wù)時(shí)如何運(yùn)行的沧侥。

在測(cè)試用例中執(zhí)行該方法,參數(shù)時(shí)一個(gè)空的List魄鸦,這個(gè)Sql的運(yùn)行肯定是失敗的宴杀。我們主要看看他的運(yùn)行過程。我們講斷點(diǎn)打在該方法上拾因。斷點(diǎn)進(jìn)入該方法旺罢。

注意,dataCollectionShareService 對(duì)象已經(jīng)被 Cglib 代理了绢记,那么他肯定會(huì)走 DynamicAdvisedInterceptor 的 intercept 方法扁达,我們斷點(diǎn)進(jìn)入該方法查看,這個(gè)方法我們已經(jīng)很屬性了蠢熄,該方法中跪解,最重要的事情就是執(zhí)行通知器或者攔截器的方法,那么签孔,該代理有通知器嗎叉讥?

有一個(gè)通知器砾跃。是什么呢?

一個(gè)事務(wù)攔截器节吮,也就是說抽高,如果通知器鏈不為空,就會(huì)依次執(zhí)行通知器鏈的方法透绩。那么 TransactionInterceptor 到底是什么呢翘骂?

該類實(shí)現(xiàn)了通知器接口,也實(shí)現(xiàn)類 MethodInterceptor 接口帚豪,并實(shí)現(xiàn)了該接口的 invoke 方法碳竟,在 DynamicAdvisedInterceptor 的 intercept 方法中,最終會(huì)調(diào)用每個(gè) MethodInterceptor 的 invoke 方法狸臣,那么莹桅,TransactionInterceptor 的 invoke 方法是如何實(shí)現(xiàn)的呢?

invoke 方法中會(huì)調(diào)用自身的 invokeWithinTransaction 方法烛亦,看名字诈泼,該方法和事務(wù)相關(guān)。該方法參數(shù)是由目標(biāo)方法煤禽,目標(biāo)類铐达,一個(gè)回調(diào)對(duì)象構(gòu)成。 那么我們就進(jìn)入該方法查看檬果,該方法很長(zhǎng):

    /**
     * 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. 獲取事務(wù)屬性瓮孙,根據(jù)事務(wù)屬性,獲取事務(wù)管理器选脊。
  2. 判斷屬性是否空杭抠,或者事務(wù)管理器是否不是 CallbackPreferringPlatformTransactionManager 類型,如果是該類型恳啥,則會(huì)執(zhí)行事務(wù)管理器的 execute 方法偏灿。
  3. 生成一個(gè)封裝了事務(wù)管理器,事務(wù)屬性角寸,方法簽名字符串菩混,事務(wù)狀態(tài)對(duì)象 的 TransactionInfo 事務(wù)信息對(duì)象忿墅。該對(duì)象會(huì)在事務(wù)回滾或者失敗時(shí)起作用扁藕。
  4. 調(diào)用目標(biāo)對(duì)象方法或者是下一個(gè)過濾器的方法。
  5. 如果方法由異常則執(zhí)行 completeTransactionAfterThrowing 方法疚脐,調(diào)用事務(wù)管理器的回滾方法亿柑。如果沒有異常,調(diào)用 commitTransactionAfterReturning 提交方法棍弄。最后返回返回值望薄。

可以說疟游,該方法就是Spring 事務(wù)的核心調(diào)用,根據(jù)目標(biāo)方法是否有異常進(jìn)行事務(wù)的回滾痕支。

那么颁虐,我們需要一行一行的看看該方法實(shí)現(xiàn)。

首先看事務(wù)的屬性卧须。

2. TransactionAttribute 事務(wù)屬性

invokeWithinTransaction 方法中調(diào)用了 自身的 getTransactionAttributeSource 方法返回一個(gè)TransactionAttributeSource 對(duì)象另绩,并調(diào)用該對(duì)象的 getTransactionAttribute 方法,參數(shù)是目標(biāo)方法和目標(biāo)類對(duì)象花嘶。首先看 getTransactionAttributeSource 方法笋籽,該方法直接返回了抽象類 TransactionAspectSupport 中定義的 TransactionAttributeSource 屬性。該屬性的是什么時(shí)候生成的我們稍后再說椭员。我們debug 后返回的是 TransactionAttributeSource 接口的實(shí)現(xiàn)類 AnnotationTransactionAttributeSource 车海,看名字,注解事務(wù)屬性資源隘击,名字起的好很重要啊侍芝。我們進(jìn)入該類查看。

這是該類的繼承機(jī)構(gòu)圖埋同。我們重點(diǎn)還是關(guān)注該類的 getTransactionAttribute 方法竭贩,該方法有抽象類 AbstractFallbackTransactionAttributeSource 也就是 AnnotationTransactionAttributeSource 的父類完成。我們看看該方法莺禁。

該方法大部分都是緩存判斷留量,最重要的一行代碼樓主已紅框標(biāo)出。computeTransactionAttribute 方法哟冬,計(jì)算事務(wù)屬性楼熄。進(jìn)入該方法查看:

該方法是返回事務(wù)屬性的核心方法,首先浩峡,根據(jù) class 和 method 對(duì)象可岂,生成一個(gè)完整的method 對(duì)象,然后調(diào)用 findTransactionAttribute 方法翰灾,參數(shù)就是該 method 對(duì)象缕粹,findTransactionAttribute 方法是抽象方法,由子類實(shí)現(xiàn)纸淮,可見 computeTransactionAttribute 是個(gè)模板方法模式平斩。那么我們就看看他的子類 AnnotationTransactionAttributeSource 是如何實(shí)現(xiàn)的。該方法調(diào)用了自身的 determineTransactionAttribute 方法咽块。該方法實(shí)現(xiàn)入下:

該方法會(huì)判斷該 Method 對(duì)象是否含有注解绘面。并循環(huán) AnnotationTransactionAttributeSource 對(duì)象的 annotationParsers 注解解析器集合,對(duì)該方法進(jìn)行解析。如果解析成功揭璃,則返回該注解元素晚凿。我想我們也已經(jīng)猜到了,這個(gè)注解解析器解析的就是 @Transactional 注解瘦馍。

3. @Transactional 注解解析器 SpringTransactionAnnotationParser

我們說AnnotationTransactionAttributeSource 對(duì)象中又多個(gè)解析器歼秽。那么這些解析器是什么時(shí)候生成的呢?構(gòu)造方法中生成的情组。

該構(gòu)造方法由一個(gè)布爾屬性哲银,然后創(chuàng)建一個(gè)鏈表,也創(chuàng)建一個(gè) SpringTransactionAnnotationParser 對(duì)象添加進(jìn)鏈表中呻惕。這樣就完成了解析器的創(chuàng)建荆责。構(gòu)造方法什么時(shí)候調(diào)用的呢?我們稍后再講亚脆。

我們看看注解解析器是怎么解析方法對(duì)象的做院。

首先根據(jù)指定的 Transactional 注解和給定的方法,調(diào)用工具方法 getMergedAnnotationAttributes 濒持,獲取方法上的注解屬性键耕。然后調(diào)用重載方法 parseTransactionAnnotation 。

可以看到柑营,該方法首先創(chuàng)建了一個(gè) RuleBasedTransactionAttribute 對(duì)象屈雄,然后一個(gè)個(gè)解析注解中的元素,并將這些元素設(shè)置到 RuleBasedTransactionAttribute 對(duì)象中官套,注意酒奶,其中有個(gè) RollbackRuleAttribute 的集合,存儲(chǔ)著該注解屬性的回滾相關(guān)的屬性奶赔。最后添加到 RuleBasedTransactionAttribute 的RollbackRules 集合中惋嚎。

到這里,就完成了解析器的解析站刑。返回了一個(gè) RuleBasedTransactionAttribute 對(duì)象另伍。

回到 攔截器的 invokeWithinTransaction 方法中,此時(shí)已經(jīng)獲取了 屬性對(duì)象绞旅。根據(jù)方法摆尝,也就是說,如果返回值是null因悲,說明該方法沒有事務(wù)注解堕汞,在 getTransactionAttribute 方法中,也會(huì)將該方法作為key 囤捻,NULL_TRANSACTION_ATTRIBUTE 作為 value臼朗,放入緩存,如果不為null蝎土,那么就將 TransactionAttribute 作為 value 放入緩存视哑。

有了事務(wù)屬性,再獲取事務(wù)管理器誊涯。也就是 determineTransactionManager 方法挡毅。

4. 事務(wù)管理器。

我們注意到暴构,調(diào)用了自身的 determineTransactionManager 方法跪呈,返回了一個(gè) PlatformTransactionManager 事務(wù)管理器。這個(gè)事務(wù)管理器就是我們?cè)谖覀兊呐渲妙愔袑懙模?/p>

那么這個(gè)事務(wù)管理器是什么呢取逾?事務(wù)管理器就是真正執(zhí)行事務(wù)回滾或提交的執(zhí)行單位耗绿,我們看看該類:

繼承圖:


結(jié)構(gòu)圖:


紅框標(biāo)注的方法就是執(zhí)行正在事務(wù)邏輯的方法,其中又封裝了數(shù)據(jù)源砾隅,也就是 JDBC 的 Connection 误阻。比如 doCommit 方法:

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

該方法步驟入下:

  1. 如果事務(wù)屬性為null 或者 容器工廠為null晴埂,則返會(huì)自身的 transactionManager 事務(wù)管理器究反。
  2. 如果都不為null,則獲取事務(wù)屬性的限定符號(hào)儒洛,根據(jù)限定符從容器中獲取 事務(wù)管理器精耐。
  3. 如果沒有限定符,則根據(jù)事務(wù)管理器的BeanName從容器中獲取琅锻。
  4. 如果都沒有卦停,則獲取自身的事務(wù)管理器,如果自身還沒有恼蓬,則從緩存中取出默認(rèn)的沫浆。如果默認(rèn)的還沒有,則從容器中獲取PlatformTransactionManager 類型的事務(wù)管理器滚秩,最后返回专执。

這里重點(diǎn)是自身的事務(wù)管理器從何而來?我們先按下不表郁油。

到這里本股,我們已經(jīng)有了事務(wù)管理器。就需要執(zhí)行 invokeWithinTransaction 下面的邏輯了桐腌≈粝裕回到 invokeWithinTransaction 方法,我們的返回值肯定滿足第一個(gè)if 條件案站,因?yàn)槲覀兊氖聞?wù)管理器不是 CallbackPreferringPlatformTransactionManager 類型的躬审。進(jìn)入if 塊。

首先創(chuàng)建一個(gè)事務(wù)信息對(duì)象。該類是什么呢承边?

屬性:


構(gòu)造方法:


該類包含了一個(gè) 事務(wù)管理器遭殉,事務(wù)屬性,事務(wù)方法字符串博助。

接著執(zhí)行回調(diào)類InvocationCallback 的 proceedWithInvocation 方法险污,該方法會(huì)執(zhí)行下一個(gè)通知器的攔截方法(如果有的話),最后執(zhí)行目標(biāo)方法富岳,這里蛔糯,目標(biāo)方法被 try 住了,如果發(fā)生異常窖式,則執(zhí)行completeTransactionAfterThrowing 方法蚁飒,并拋出異常,在 finally 塊中執(zhí)行清理工作萝喘。如果成功執(zhí)行飒箭,則執(zhí)行
commitTransactionAfterReturning 方法。最后返回目標(biāo)方法返回值蜒灰。

我們重點(diǎn)看看 completeTransactionAfterThrowing 方法和 commitTransactionAfterReturning 方法弦蹂。

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

該方法主要內(nèi)容在紅框中强窖,首先判斷該事務(wù)對(duì)象是否和該異常匹配凸椿,如果匹配,則回滾翅溺,否則脑漫,則提交。那么咙崎,是否匹配的邏輯是怎么樣的呢优幸?我們的事務(wù)屬性是什么類型的?RuleBasedTransactionAttribute 褪猛,就是我們剛剛創(chuàng)建解析注解后創(chuàng)建的网杆。那么我就看看該類的 rollbackOn 方法:

首先,循環(huán)解析注解時(shí)添加進(jìn)集合的回滾元素伊滋。并遞歸調(diào)用RollbackRuleAttribute 的 getDepth 方法碳却,如果這個(gè)異常的名字和注解中的異常名字匹配,則返回該異常的回滾類型笑旺。最后判斷昼浦,如果沒有匹配到,則調(diào)用父類的 rollbackOn 方法筒主,如果匹配到了关噪,并且該屬性類型不是 NoRollbackRuleAttribute 類型鸟蟹,返回true。表示匹配到了使兔,可以回滾建钥。那么父類的 rollbackOn 方法肯定就是默認(rèn)的回滾方法了。

這是父類的 rollbackOn 方法:

該方法判斷火诸,該異常如果是 RuntimeException 類型異辰跽耄或者 是 Error 類型的荠察,就回滾置蜀。這就是默認(rèn)的回滾策略。

那么我們的方法肯定是匹配的 RuntimeException 異常悉盆,就會(huì)執(zhí)行下面的方法盯荤。

可以看到,這行代碼就是執(zhí)行了我們的事務(wù)管理器的 rollback 方法焕盟,并且攜帶了事務(wù)狀態(tài)對(duì)象秋秤。該方法實(shí)現(xiàn)在抽象類 AbstractPlatformTransactionManager 中,調(diào)用了自身的 processRollback 方法做真正的實(shí)現(xiàn)脚翘。

該方法首先切換事務(wù)狀態(tài)灼卢,其實(shí)就是關(guān)閉SqlSession。

然后調(diào)用 doRollback 方法来农。

首先鞋真,從狀態(tài)對(duì)象中獲取數(shù)據(jù)庫(kù)連接持有對(duì)象,然后獲取數(shù)據(jù)庫(kù)連接沃于,調(diào)用 Connection 的 rollback 方法涩咖,也就是我們學(xué)習(xí)JDBC 時(shí)使用的方法。最后修改事務(wù)的狀態(tài)繁莹。

到這里檩互,事務(wù)的回滾就結(jié)束了。

那么咨演,事務(wù)時(shí)如何提交的呢闸昨?

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

該方法簡(jiǎn)單的調(diào)用了事務(wù)管理器的 commit 方法薄风。

AbstractPlatformTransactionManager 的 commit 方法零院。

首先判斷了事務(wù)的狀態(tài),如果狀態(tài)不匹配村刨,則調(diào)用回滾方法告抄。如果狀態(tài)正常,執(zhí)行 processCommit 方法嵌牺。該方法很長(zhǎng)打洼,樓主只截取其中一段:

首先龄糊,commit 之前做一些狀態(tài)切換工作。最重要的是執(zhí)行 doCommit 方法募疮,如果異常了炫惩,則回滾。那么 DataSourceTransactionManager 的 doCommit 是如何執(zhí)行的呢阿浓?

可以看到他嚷,底層也是調(diào)用 JDBC 的 Connection 的 commit 方法。

到這里芭毙,我們就完成了數(shù)據(jù)庫(kù)的提交筋蓖。

7. 事務(wù)運(yùn)行之前做了哪些工作?

從前面的分析退敦,我們已經(jīng)知道了事務(wù)是如何運(yùn)行的粘咖,如何回滾的,又是如何提交的侈百。在這是交互型的框架里瓮下,事務(wù)系統(tǒng)肯定做了很多的準(zhǔn)備工作,同時(shí)钝域,我們留下了很多的疑問讽坏,比如事務(wù)管理器從何而來? TransactionAttributeSource 屬性何時(shí)生成例证?AnnotationTransactionAttributeSource 構(gòu)造什么時(shí)候調(diào)用路呜?

我們一個(gè)個(gè)的來解釋。

在Spring 中战虏,有一個(gè)現(xiàn)成的類拣宰,ProxyTransactionManagementConfiguration,我們看看該類:

看到這個(gè)類烦感,應(yīng)該可以解開我們的疑惑巡社,這個(gè)類標(biāo)注了配置注解,會(huì)在IOC的時(shí)候?qū)嵗擃愂秩ぃ擃愔挟a(chǎn)生了幾個(gè)Bean晌该,比如事務(wù)攔截器 TransactionInterceptor,創(chuàng)建了 AnnotationTransactionAttributeSource 對(duì)象绿渣,并向事務(wù)攔截器添加了事務(wù)管理器朝群。最后,將事務(wù)攔截器封裝成通知器中符。那么姜胖,剩下最后一個(gè)問題就是,事務(wù)管理器從何而來淀散?答案是他的父類 AbstractTransactionManagementConfiguration :

該類也是個(gè)配置類右莱,自動(dòng)注入了 TransactionManagementConfigurer 的配置集合蚜锨,而并且尋找了配置 EnableTransactionManagement 注解的類,而我們?cè)谖覀兊捻?xiàng)目中就是按照這個(gè)標(biāo)準(zhǔn)來實(shí)現(xiàn)的:

我們關(guān)聯(lián)這兩個(gè)類就能一目了然慢蜓,Spring在啟動(dòng)的時(shí)候亚再,會(huì)加載這兩個(gè)配置類,在對(duì) AbstractTransactionManagementConfiguration 的 setConfigurers 方法進(jìn)行注入的時(shí)候晨抡,會(huì)從容器中找到對(duì)應(yīng)類型的配置氛悬,并調(diào)用配置類的 annotationDrivenTransactionManager 方法,也就是我們實(shí)現(xiàn)的方法耘柱,獲取到我們創(chuàng)建的 DataSourceTransactionManager 類如捅。這樣,我們的事務(wù)攔截器相關(guān)的類就完成了在Spring中的依賴關(guān)系帆谍。

但是伪朽,這個(gè)時(shí)候Spring中的事務(wù)運(yùn)行還沒有搭建完成轴咱。比如什么時(shí)候創(chuàng)建類的代理汛蝙?根據(jù)什么創(chuàng)建代理,因?yàn)槲覀冎榔臃危琒pring 中的事務(wù)就是使用AOP來完成的窖剑,必須使用動(dòng)態(tài)代理或者 Cglib 代理來對(duì)目標(biāo)方法進(jìn)行攔截。

這就要復(fù)習(xí)我們之前的Spring IOC 的啟動(dòng)過程了戈稿。Spring 在創(chuàng)建bean的時(shí)候西土,會(huì)對(duì)每個(gè)Bean 的所有方法進(jìn)行遍歷,如果該方法匹配系統(tǒng)中任何一個(gè)攔截器的切點(diǎn)鞍盗,就創(chuàng)建一個(gè)該Bean的代理對(duì)象需了。并且會(huì)將對(duì)應(yīng)的通知器放入到代理類中。以便在執(zhí)行代理方法的時(shí)候進(jìn)行攔截般甲。

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

  1. 在對(duì)bean 進(jìn)行初始化的時(shí)候會(huì)執(zhí)行 AutowireCapableBeanFactory 接口的 applyBeanPostProcessorsAfterInitialization 的方法肋乍,其中會(huì)遍歷容器中所有的bean后置處理器,后置處理器會(huì)調(diào)用 postProcessAfterInitialization 方法對(duì)bean進(jìn)行處理敷存。
  1. 在處理過程中墓造,對(duì)bean 進(jìn)行包裝,也就是代理的創(chuàng)建锚烦,調(diào)用 getAdvicesAndAdvisorsForBean 方法觅闽,該方法會(huì)根據(jù)bean的信息獲取到對(duì)應(yīng)的攔截器并創(chuàng)建代理,創(chuàng)建代理的過程我們之前已經(jīng)分析過了涮俄,不再贅述蛉拙。
  1. 尋找匹配攔截器過程:首先找到所有的攔截器,然后彻亲,根據(jù)bean的信息進(jìn)行匹配孕锄。
  1. 匹配的過程就是室叉,找到目標(biāo)類的所有方法,遍歷硫惕,并調(diào)用攔截器的方法匹配器對(duì)每個(gè)方法進(jìn)行匹配茧痕。方法匹配器就是事務(wù)攔截器中的 BeanFactoryTransactionAttributeSourceAdvisor 類,該類封裝了 AnnotationTransactionAttributeSource 用于匹配事務(wù)注解的匹配器恼除。
  1. 最終調(diào)用方法匹配器中封裝的注解解析器解析方法踪旷,判斷方法是否含有事務(wù)注解從而決定是否生成代理:

到這里,就完成了所有事務(wù)代理對(duì)象的創(chuàng)建豁辉。

項(xiàng)目中的每個(gè)Bean都有了代理對(duì)象令野,在執(zhí)行目標(biāo)方法的時(shí)候,代理類會(huì)查看目標(biāo)方法是否匹配代理中攔截器的方法匹配器中定義的切點(diǎn)徽级。如果匹配气破,則執(zhí)行攔截器的攔截方法,否則餐抢,直接執(zhí)行目標(biāo)方法现使。這就是含有事務(wù)注解和不含有事務(wù)注解方法的執(zhí)行區(qū)別。

到這里旷痕,我們還剩下最后一個(gè)問題碳锈,我們知道,在分析mybatis 的時(shí)候欺抗,mybatis 也有自己的事務(wù)管理器售碳,那么他們?nèi)诤现螅麄兊氖聞?wù)管理權(quán)在誰的手上绞呈,又是根據(jù)什么切換的呢贸人?

8. mybatis 和 Spring 的事務(wù)管理權(quán)力之爭(zhēng)

我們之前說過,在Spring中佃声,mybatis 有 SqlSessionTemplate 代理執(zhí)行艺智,其實(shí)現(xiàn)類動(dòng)態(tài)代理的 InvocationHandler 方法,那么最重要的方法就是 invoke 方法秉溉,其實(shí)這個(gè)方法我們已經(jīng)看過了力惯,今天再看一遍:

我們今天重點(diǎn)關(guān)注是否提交(報(bào)錯(cuò)肯定回滾),其中紅框標(biāo)出來的 if 判斷召嘶,就是判斷這個(gè)事務(wù)到底是Spring 來提交父晶,還是 mybatis 來提交,那么我們看看這個(gè)方法 isSqlSessionTransactional :

該方法從Spring 的容器中取出持有 SqlSession 的 持有類弄跌,判斷Spirng 持有的 SqlSession 和 Mybatis 持有的是否是同一個(gè)甲喝,如果是,則交給Spring铛只,否則埠胖,Mybatis 自己處理糠溜。可以說很合理直撤。

總結(jié)

今天的這篇文章可以說非常的長(zhǎng)非竿,我們分析了 SpringBoot 的事務(wù)運(yùn)行過程,事務(wù)環(huán)境的搭建過程谋竖,mybatis 的事務(wù)和 Spring 事務(wù)如何協(xié)作红柱。知道了整個(gè)事務(wù)其實(shí)是建立在AOP的基礎(chǔ)之上,其核心類就是 TransactionInterceptor蓖乘,該類就是 invokeWithinTransaction 方法是就事務(wù)處理的核心方法锤悄,其中封裝了我們創(chuàng)建的 DataSourceTransactionManager 對(duì)象,該對(duì)象就是執(zhí)行回滾或者提交的執(zhí)行單位 其實(shí)嘉抒,TransactionInterceptor 和我們平時(shí)標(biāo)注 @Aspect 注解的類的作用相同零聚,就是攔截指定的方法,而在
TransactionInterceptor 中是通過是否標(biāo)有事務(wù)注解來決定的些侍。如果一個(gè)類中任意方法含有事務(wù)注解隶症,那么這個(gè)方法就會(huì)被代理。而Mybatis 的事務(wù)和Spring 的事務(wù)協(xié)作則根據(jù)他們的SqlSession 是否是同一個(gè)SqlSession 來決定的娩梨,如果是同一個(gè)沿腰,則交給Spring览徒,如果不是狈定,Mybatis 則自己處理。

通過閱讀源碼习蓬,我們已經(jīng)弄清楚了SpirngBoot 整個(gè)事務(wù)的運(yùn)行過程纽什。實(shí)際上,Spring 的其他版本也大同小異躲叼。底層都是 TransactionInterceptor 芦缰,只不過入口不一樣。我相信枫慷,在以后的工作中让蕾,如果遇到了Spring事務(wù)相關(guān)的問題,再也不會(huì)感到無助了或听,因?yàn)橹懒嗽硗叮梢陨钊氲皆创a中查看吝沫。

到這里,樓主的 Spring ,mybatis 默垄,Tomcat 的源碼閱讀之路暫時(shí)就告一段落了。源碼只要領(lǐng)會(huì)精華即可。還有其他的知識(shí)需要花費(fèi)更多的時(shí)間學(xué)習(xí)。比如并發(fā)庇配,JVM.

good luck!I苄@袒拧!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柬批,一起剝皮案震驚了整個(gè)濱河市卿闹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萝快,老刑警劉巖锻霎,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異揪漩,居然都是意外死亡旋恼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門奄容,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冰更,“玉大人,你說我怎么就攤上這事昂勒∈裣福” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵戈盈,是天一觀的道長(zhǎng)奠衔。 經(jīng)常有香客問我,道長(zhǎng)塘娶,這世上最難降的妖魔是什么归斤? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮刁岸,結(jié)果婚禮上脏里,老公的妹妹穿的比我還像新娘。我一直安慰自己虹曙,他們只是感情好迫横,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酝碳,像睡著了一般矾踱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上击敌,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天介返,我揣著相機(jī)與錄音,去河邊找鬼。 笑死圣蝎,一個(gè)胖子當(dāng)著我的面吹牛刃宵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播徘公,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼牲证,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了关面?” 一聲冷哼從身側(cè)響起坦袍,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎等太,沒想到半個(gè)月后捂齐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缩抡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年奠宜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞻想。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡压真,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蘑险,到底是詐尸還是另有隱情滴肿,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布佃迄,位于F島的核電站泼差,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏和屎。R本人自食惡果不足惜拴驮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柴信。 院中可真熱鬧,春花似錦宽气、人聲如沸随常。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绪氛。三九已至,卻和暖如春涝影,著一層夾襖步出監(jiān)牢的瞬間枣察,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留序目,地道東北人臂痕。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像猿涨,于是被迫代替她去往敵國(guó)和親握童。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理叛赚,服務(wù)發(fā)現(xiàn)澡绩,斷路器,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,822評(píng)論 6 342
  • 1. 簡(jiǎn)介 1.1 什么是 MyBatis 俺附? MyBatis 是支持定制化 SQL肥卡、存儲(chǔ)過程以及高級(jí)映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,523評(píng)論 0 4
  • 今天的圖,男事镣,34歲召调!主題:森林深處 第100幅的分析,本來想留給我老公的蛮浑,怎奈他不愿意向我敞開自己...此處應(yīng)該...
    Nina張閱讀 1,693評(píng)論 0 1
  • 5月29日晨讀感悟 ---生活中處處都是談判 想到談判唠叛,就像港劇《談判專家》里面的場(chǎng)景,這些西裝筆挺的談判專家沮稚,個(gè)...
    shmily由由and花花閱讀 165評(píng)論 0 0