Mybatis源碼之美:3.5.6.resultMap元素的解析過(guò)程(一)

解析resultMap元素的過(guò)程

我來(lái)了

在前面幾篇文章中,我們?cè)敿?xì)的了解了resultMap元素的屬性和子元素定義,過(guò)程比較復(fù)雜,但總算還是有些收獲.

我也是寫(xiě)到resultMap元素的時(shí)候,才忽然想明白一個(gè)道理:

只有實(shí)實(shí)在在的了解每個(gè)屬性和元素的作用,我們才能更好的去理解mybatis解析resultMap時(shí)所做的一些判斷和處理.

所以,雖然介紹resultMap元素的用法著實(shí)費(fèi)了很大的功夫,但我覺(jué)得還是值得的.

辛苦了那么久,今天來(lái)點(diǎn)不一樣的,換一個(gè)方式看看能不能更有意思的去學(xué)習(xí)源碼,

那么,讓我們開(kāi)始吧~

一番操作

閃亮登場(chǎng)環(huán)節(jié)

如果我們將mybatis比作一個(gè)創(chuàng)業(yè)公司,resultMap元素就好比是mybatis的一個(gè)項(xiàng)目部,那么resultMap元素的各個(gè)子元素就是形形色色的員工,角色不同,職責(zé)也不同.

resultMap項(xiàng)目負(fù)責(zé)對(duì)接外部數(shù)據(jù),并完成將外部數(shù)據(jù)轉(zhuǎn)換為java對(duì)象的業(yè)務(wù).

mybatis公司初創(chuàng)時(shí),針對(duì)resultMap項(xiàng)目部,招進(jìn)來(lái)許許多多的result元素,剛開(kāi)始時(shí)間短,看不出來(lái)什么不同,大家都負(fù)責(zé)最基礎(chǔ)的普通屬性轉(zhuǎn)換工作.

result

但是隨著公司業(yè)務(wù)的擴(kuò)張,慢慢地,部分優(yōu)秀的result元素開(kāi)始展露頭角,因?yàn)樵谝恍﹩?wèn)題上總是能提出一些具有建設(shè)性的意見(jiàn),于是得到了老板的賞識(shí),升級(jí)成了id元素.

雖然id元素干的還是和result元素一樣的活,但是,多多少少身份是不一樣了,至少在開(kāi)會(huì)時(shí),id元素開(kāi)始擁有了決策投票權(quán),甚至所有的id元素在一起就能拍板決定resultMap項(xiàng)目部的大小事宜.

id元素

除此之外呢,還有一些result元素深耕自己的崗位,日積月累的,在某一方面取得了不菲的成就,成為了能夠獨(dú)當(dāng)一面的大牛,比如collectionassociation元素,他們分別成為了處理復(fù)雜對(duì)象和處理集合對(duì)象的業(yè)務(wù)領(lǐng)域?qū)<?被公司委以重任,根據(jù)公司需要,他們隨時(shí)可以拉起團(tuán)隊(duì),成立一個(gè)和resultMap職能一樣的項(xiàng)目部.

collection和association元素

公司內(nèi)還有一個(gè)比較特殊的discriminator元素,他是公司老板的親大爺,見(jiàn)多識(shí)廣,位高權(quán)重,現(xiàn)擔(dān)任傳達(dá)室大爺一職,你別看大爺上年紀(jì)了,但是本事可不小,一些比較復(fù)雜多變的問(wèn)題基本都得靠discriminator大爺來(lái)解決,當(dāng)然大爺不會(huì)真動(dòng)手干活,基本就是動(dòng)動(dòng)嘴皮子,分析分析業(yè)務(wù),把業(yè)務(wù)合情合理的分配給整天屁顛屁顛的跟在他屁股后面的case元素來(lái)處理.

discriminator

case元素也是從result元素的位置上一點(diǎn)點(diǎn)摸爬滾打走上來(lái)的,他們雖然是discriminator元素的兒子,但你可不要一廂情愿的以為case元素是不學(xué)無(wú)術(shù)的富二代,這些case元素也是個(gè)頂個(gè)兒的人才,個(gè)個(gè)都能獨(dú)當(dāng)一面.

case

甚至在resultMap內(nèi)部,大家將collection,association以及case三元素稱(chēng)為嵌套映射的三巨頭,人送外號(hào)小resultMap.

除此之外呢,傳說(shuō)在resultMap內(nèi)部還有一個(gè)神龍見(jiàn)首不見(jiàn)尾的技術(shù)總監(jiān),名為constructor,constructor元素可不簡(jiǎn)單,他控制著resultMap項(xiàng)目部工作成果的交付,公司可以沒(méi)有他的身影,但是卻一定流傳著他的大名,他一現(xiàn)身,如何將數(shù)據(jù)轉(zhuǎn)換為java對(duì)象這件事就得按照他的想法來(lái)了.

constructor

constructor手下有一個(gè)單獨(dú)的部門(mén)用來(lái)完成java對(duì)象的構(gòu)建工作,在這個(gè)部門(mén)完成java對(duì)象的構(gòu)建操作之前,其他元素啥事都不能干,都得等著他們.

這個(gè)部門(mén)里待的元素雖然也是從result這個(gè)職位上走過(guò)來(lái)的,但是地位卻有所不同.

公司的元老級(jí)元素arg就是其中之一,公司初創(chuàng)的時(shí)候他就在了,一步步的從result元素走到這個(gè)位置,雖然本事沒(méi)啥長(zhǎng)進(jìn),職位也沒(méi)往上升,但多多少少算是個(gè)老人了,大家或多或少的也給他點(diǎn)面子.

arg

但是另一個(gè)元素,idArg就不一樣了,他是arg元素中的精英,精英中的精英,人稱(chēng)小技術(shù)總監(jiān),他可是和id元素一樣,具有公司的決策投票權(quán)的.

idArg

所以你別看idArgid兩個(gè)元素表面上只能干最普通的活,但是人家可以參與項(xiàng)目部決策,拿項(xiàng)目部分紅的,甚至在一些人眼里,他倆能代表整個(gè)resultMap項(xiàng)目部.

物以類(lèi)聚,元素以群分

雖然resultMap項(xiàng)目部不大,在mybaits公司內(nèi)部,干的也是數(shù)據(jù)清洗的臟話(huà)累活,手底下干活的元素也不多,但是他的的確確是mybaits公司的核心項(xiàng)目部.

通常來(lái)說(shuō),你要是有數(shù)據(jù)清洗的項(xiàng)目,你就找mybaits公司的前臺(tái)填個(gè)表,上面詳細(xì)的列上你需要哪些人,干哪些事.

你要是不知道這個(gè)單子應(yīng)該怎么填,你就看看前面的幾篇文章,或者看看resultMap項(xiàng)目部的員工介紹:

比如id,result這倆哥們,他倆就非常擅長(zhǎng)把數(shù)據(jù)轉(zhuǎn)換為簡(jiǎn)單的屬性值,一般情況下,簡(jiǎn)單的屬性轉(zhuǎn)換操作,找這哥倆準(zhǔn)沒(méi)錯(cuò).

id和result

要是有些數(shù)據(jù)的構(gòu)建比較復(fù)雜,那就得找技術(shù)總監(jiān)constructor元素處理了,constructor人狠話(huà)不多,做事干凈利索,拿個(gè)小本本稍微記一下,就把數(shù)據(jù)下放到idArgarg手里去了.

看戲

前面咱也說(shuō)了idArg,arg,id,result這四個(gè)哥們干的都是些簡(jiǎn)單的數(shù)據(jù)處理操作.你要是非得拿復(fù)雜數(shù)據(jù)來(lái)找他們處理,idArgarg倆哥們倒是也能解決,他倆直接把數(shù)據(jù)轉(zhuǎn)包給其他resultMap項(xiàng)目部,讓轉(zhuǎn)包公司處理就行了.

ok

但是idresult這倆哥們就不一樣了,他倆要是能搭理你一下都算我輸.

給你看個(gè)寶貝

所以說(shuō),你要是真有些數(shù)據(jù)要轉(zhuǎn)換為復(fù)雜對(duì)象,還是得找collectionassociation元素,這倆哥們?nèi)?簡(jiǎn)單的活他倆能干,如果是復(fù)雜的活,他倆轉(zhuǎn)包也好,自己拉團(tuán)隊(duì)也好,都能給你整的明明白白的.

悠閑

要是有些數(shù)據(jù)比較復(fù)雜,涉及到的場(chǎng)景是會(huì)發(fā)生變化的,那這時(shí)候你就得找年紀(jì)大,經(jīng)驗(yàn)足,察言觀色能力的強(qiáng)的discriminator大爺了,大爺手一背,墨鏡一戴,就為涉及到的每一種場(chǎng)景,都分配了一個(gè)case元素.

大爺碼數(shù)據(jù)

跟屁蟲(chóng)case元素接到數(shù)據(jù)之后,轉(zhuǎn)包也好,自己拉團(tuán)隊(duì)也好,處理起來(lái)那也是一點(diǎn)也不含糊的,毫不遜色于collectionassociation元素.

你看,這就是resultMap公司,能把數(shù)據(jù)給你安排的明明白白.

真棒

現(xiàn)在你知道單子怎么填了吧,你把單子填上之后,mybatis就開(kāi)始根據(jù)你的單子給你立項(xiàng),成立一個(gè)單獨(dú)的resultMap項(xiàng)目部來(lái)完成你的需求.

解析resultMap元素的基本流程

要知道,在一個(gè)resultMap項(xiàng)目里面,真正干活的元素也就那么幾個(gè),像discriminatorconstructor這種元素,他們屬于領(lǐng)導(dǎo)型,主要是負(fù)責(zé)分配活.

真正干活的是那些從result位置上一步一步摸爬滾打上來(lái)的元素:id,result,idArg,arg,association,collection,以及case.

比如id,result,idArg,arg這四個(gè)元素就是腳踏實(shí)地,老老實(shí)實(shí)的負(fù)責(zé)簡(jiǎn)單數(shù)據(jù)轉(zhuǎn)換的工作,雖然職位名稱(chēng)不一樣吧,但是干的活基本一致.

剩下的嵌套映射三巨頭的工作和上面四個(gè)又有點(diǎn)不一樣,因?yàn)?code>三巨頭是能帶團(tuán)隊(duì)的,所以他們自己可以根據(jù)工作需要單獨(dú)成立一個(gè)子resultMap項(xiàng)目部,或者找個(gè)其他的resultMap項(xiàng)目部來(lái)幫他們完成工作.

下面是resultMap的所有子元素定義:

resultMap的子元素

我們將上面的思維導(dǎo)圖去重之后重新整理:

思維導(dǎo)圖

娛樂(lè)時(shí)間結(jié)束,言歸正傳.

我們可以大致將resultMap的子元素分為兩類(lèi):一類(lèi)是不負(fù)責(zé)實(shí)際屬性轉(zhuǎn)換操作的標(biāo)志性的元素,一類(lèi)是實(shí)際完成屬性轉(zhuǎn)換的元素.

標(biāo)志性元素discriminatorconstructor因?yàn)槎x和作用完全不同,所以在解析時(shí)需要單獨(dú)處理,剩余的其他元素則可以放在一起處理.

從這個(gè)角度上出發(fā),我們可以將resultMap的子元素分為三部分來(lái)解析,分別是constuctor元素,discriminator元素以及剩余的其他元素.

實(shí)際上,mybatis也是這樣解析的.

resultMap元素的解析入口

XMLMapperBuilderconfigurationElement()方法是resultMap元素的解析入口:

private void configurationElement(XNode context) {
    try {
        // 省略...
        // 解析并注冊(cè)resultMap元素
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // 省略...

    } catch (Exception e) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
}

configurationElement()方法將從mapper文件中獲取到的所有resultMap元素定義一股腦的交給了resultMapElements()方法來(lái)完成解析操作:

private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
        try {
            // 解析ResultMap節(jié)點(diǎn)
            resultMapElement(resultMapNode);
        } catch (IncompleteElementException e) {
            // ignore, it will be retried
            // 在內(nèi)部實(shí)現(xiàn)中,未完成解析的節(jié)點(diǎn)將會(huì)被放至Configuration#incompleteResultMaps中
        }
    }
}

resultMapElements()方法則依次將每個(gè)resultMap元素定義交給resultMapElement()方法來(lái)完成resultMap元素的解析注冊(cè)操作:

/**
    * 解析ResultMap節(jié)點(diǎn)
    *
    * @param resultMapNode ResultMap節(jié)點(diǎn)
    */
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping>emptyList(), null);
}

resultMapElement方法簡(jiǎn)介

不過(guò),實(shí)際完成解析工作的方法是resultMapElement()方法的另一個(gè)重載實(shí)現(xiàn):

/**
 * 解析ResultMap元素,關(guān)于ResultMap的各個(gè)子元素的作用可以參考文檔{@link https://blog.csdn.net/u012702547/article/details/54599132}
 * <p>
 * 該方法并不是單純的只用于解析ResultMap元素,而是用于解析具有ResultMap性質(zhì)的元素,該方法的調(diào)用方,目前 有兩個(gè),一個(gè)是用來(lái)解析`ResultMap`元素,
 * 另一個(gè)使用該方法來(lái)解析association/collection/discriminator的case元素。
 *
 * @param resultMapNode            resultMapNode節(jié)點(diǎn)
 * @param additionalResultMappings 現(xiàn)有的resultMapping結(jié)合
 * @param enclosingType            返回類(lèi)型
 */
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 獲取唯一標(biāo)志,有趣的是association/collection/discriminator的case元素都沒(méi)有ID屬性,所以該ID會(huì)根據(jù)嵌套的上下文來(lái)生成。
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());

    // 獲取返回類(lèi)型 依次讀取:【type】>【ofType】>【resultType】>【javaType】
    String type = resultMapNode.getStringAttribute("type",
            resultMapNode.getStringAttribute("ofType",
                    resultMapNode.getStringAttribute("resultType",
                            resultMapNode.getStringAttribute("javaType"))));

    // 獲取當(dāng)前ResultMap是否繼承了其他ResultMap
    String extend = resultMapNode.getStringAttribute("extends");

    // 獲取自動(dòng)映射標(biāo)志
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

    // 解析出返回類(lèi)型
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
        // 嵌套映射時(shí),外部對(duì)象屬性類(lèi)型定義優(yōu)先級(jí)較低
        typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }

    Discriminator discriminator = null;

    // 返回結(jié)果定義
    List<ResultMapping> resultMappings = new ArrayList<>();
    // 添加所有額外的ResultMap集合
    resultMappings.addAll(additionalResultMappings);

    List<XNode> resultChildren = resultMapNode.getChildren();
    // 開(kāi)始處理ResultMap中的每一個(gè)子節(jié)點(diǎn)
    for (XNode resultChild : resultChildren) {
        // 獲取每一個(gè)ResultMap的子節(jié)點(diǎn) 處理constructor節(jié)點(diǎn),該節(jié)點(diǎn)用來(lái)配置構(gòu)造方法
        if ("constructor".equals(resultChild.getName())) {
            // 處理constructor節(jié)點(diǎn)
            processConstructorElement(resultChild, typeClass, resultMappings);

        } else if ("discriminator".equals(resultChild.getName())) {
            // 處理discriminator節(jié)點(diǎn)(鑒別器)
            // 通過(guò)配置discriminator節(jié)點(diǎn)可以實(shí)現(xiàn)根據(jù)查詢(xún)結(jié)果動(dòng)態(tài)生成查詢(xún)語(yǔ)句的功能
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);

        } else {
            // 獲取ID標(biāo)簽
            List<ResultFlag> flags = new ArrayList<>();
            if ("id".equals(resultChild.getName())) {
                // 添加ID標(biāo)記
                flags.add(ResultFlag.ID);
            }

            // 添加ResultMapping配置
            resultMappings.add(
                    buildResultMappingFromContext(
                            resultChild
                            , typeClass
                            , flags
                    )
            );
        }
    }

    // 構(gòu)建ResultMap解析器
    ResultMapResolver resultMapResolver = new ResultMapResolver(
            builderAssistant
            , id /*resultMap的ID*/
            , typeClass /*返回類(lèi)型*/
            , extend /*繼承的ResultMap*/
            , discriminator /*鑒別器*/
            , resultMappings /*內(nèi)部的ResultMapping集合*/
            , autoMapping /*自動(dòng)映射*/
    );

    try {
        // 解析ResultMap
        return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
        // 解析ResultMap發(fā)生異常,將獎(jiǎng)蓋ResultMap放入未完成解析的ResultMap集合.
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
    }
}

resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType)方法最終會(huì)返回一個(gè)ResultMap對(duì)象,該方法的實(shí)現(xiàn)比較長(zhǎng),我們一點(diǎn)一點(diǎn)的看.

該方法返回的ResultMap對(duì)象維護(hù)了整個(gè)resultMap元素中的所有配置,他的屬性很多,功能也很強(qiáng)大,具體的作用我們會(huì)在后續(xù)的解析過(guò)程中給出。

我們先要了解的第一點(diǎn)是:resultMapElement方法解析的resultMap元素是指所有具有resultMap元素性質(zhì)的元素,因此resultMapElement方法還被用來(lái)解析association,collection以及case元素.

resultMapElement方法的入?yún)⒔榻B

resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType)方法有三個(gè)入?yún)?分別是resultMapNode,additionalResultMappings以及enclosingType.

resultMapNode

其中resultMapNode比較好理解,他表示所有具有resultMap元素性質(zhì)的元素定義,注意,他不是單純的只代表resultMap元素,而是表示所有具有resultMap性質(zhì)的元素,
比如他還可以表示association,collection以及discriminatorcase元素:

<!ELEMENT resultMap   (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ELEMENT association (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ELEMENT collection  (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ELEMENT case        (constructor?,id*,result*,association*,collection*, discriminator?)>

additionalResultMappings

additionalResultMappings表示現(xiàn)有的ResultMapping集合,該參數(shù)只有在解析discriminator元素時(shí)才有數(shù)據(jù),其他時(shí)候均為空集合.

更多關(guān)于additionalResultMappings參數(shù)的介紹,我們放在解析discriminator子元素的內(nèi)容中來(lái)講.

因?yàn)楦鶕?jù)DTD定義來(lái)看,為具有resultMap性質(zhì)的元素配置discriminator子元素時(shí),discriminator子元素必須聲明在元素的尾部:

resultMap性質(zhì)的元素

這樣我們?cè)诮馕鰰r(shí),必須是先解析出其余的元素配置,才會(huì)解析discriminator子元素.

enClosingType

enClosingType表示當(dāng)前正在解析resultMap所屬resultMap對(duì)應(yīng)的java類(lèi)型,該參數(shù)有可能為空.

假設(shè)我們現(xiàn)有如下resultMap配置:

<resultMap id="userWithNotNullColumn" type="org.apache.learning.result_map.association.User" autoMapping="true">
    <association property="role" column="role_id" resultMap="role" columnPrefix="role_" notNullColumn="name"/>
</resultMap>

在我們解析iduserWithNotNullColumnresultMap元素時(shí),因?yàn)?code>resultMap不是嵌套的結(jié)果映射配置,他沒(méi)有所屬的resultMap,所以在解析該元素是enclosingType參數(shù)為null.

但是當(dāng)我們解析嵌套在resultMap內(nèi)部的association元素時(shí),因?yàn)樵撛貙儆?code>id為userWithNotNullColumnresultMap元素,所以enclosingType參數(shù)的值是org.apache.learning.result_map.association.User.

總結(jié)

additionalResultMappingsenClosingType這兩個(gè)屬性可能現(xiàn)在比較難理解,但是當(dāng)我們深入到resultMap元素的解析過(guò)程之后,我們就會(huì)很容易的理解這兩個(gè)參數(shù)的含義.

resultMapElement方法的詳解(解析resultMap元素)

了解了方法入?yún)⒅?我們回到resultMapElement()方法的解析過(guò)程中來(lái):

// 獲取唯一標(biāo)志,有趣的是association/collection/discriminator的case元素都沒(méi)有ID屬性,所以該ID會(huì)根據(jù)嵌套的上下文來(lái)生成。
String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());

在解析這種具有resultMap性質(zhì)的元素的時(shí)候,mybatis首先會(huì)讀取他的id屬性,這個(gè)id屬性的值將會(huì)作為生成該元素唯一標(biāo)志的依據(jù).

不過(guò),有一點(diǎn)需要注意,除了resultMap元素以外,association,collection以及discriminatorcase元素都沒(méi)有提供id屬性的定義,他們唯一標(biāo)志的生成是根據(jù)元素定義在DOM樹(shù)中實(shí)際所處的位置來(lái)確定的,大致實(shí)現(xiàn)原理就是遞歸拼接元素類(lèi)型和名稱(chēng)直到頂層元素為止,具體實(shí)現(xiàn)代碼如下:

/**
    * 基于元素的層級(jí)結(jié)構(gòu)生成唯一標(biāo)志,
    * 大概就是這樣:
    * mapper_resultMap[test_resultMap]_collection[arrays]
    */
public String getValueBasedIdentifier() {
    StringBuilder builder = new StringBuilder();
    XNode current = this;
    // 一直遞歸處理到頂級(jí)元素
    while (current != null) {
        if (current != this) {
            builder.insert(0, "_");
        }
        // 按照優(yōu)先級(jí)一次讀取 id, value ,property屬性
        String value = current.getStringAttribute(
                "id",
                current.getStringAttribute(
                        "value",
                        current.getStringAttribute(
                                "property"
                                , null
                        )
                )
        );
        // 將value值中所有的.都替換成下劃線(xiàn)
        // [value]
        if (value != null) {
            value = value.replace('.', '_');
            builder.insert(0, "]");
            builder.insert(0,
                    value);
            builder.insert(0, "[");
        }
        // 元素名稱(chēng)[value]
        builder.insert(0, current.getName());
        current = current.getParent();
    }
    return builder.toString();
}

在完成了ResultMap對(duì)象的唯一標(biāo)志的生成工作之后,Mybatis接著會(huì)獲取該元素所對(duì)應(yīng)的java類(lèi)型,因?yàn)椴煌?lèi)型的元素對(duì)于java類(lèi)型定義的屬性名稱(chēng)也有些許的不同之處,因此Mybatis在獲取java類(lèi)型的時(shí)候會(huì)按照順序依次讀取type,ofType,resultType,javaType屬性:

// 獲取返回類(lèi)型 優(yōu)先級(jí):【type】>【ofType】>【resultType】>【javaType】
String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
                resultMapNode.getStringAttribute("resultType",
                        resultMapNode.getStringAttribute("javaType"))));

在獲取到該元素的java類(lèi)型之后,Mybatis會(huì)通過(guò)讀取該元素定義的extends屬性,獲取當(dāng)前元素所繼承的resultMap元素的定義。

之后再通過(guò)autoMapping屬性來(lái)獲取當(dāng)前resultMap的自動(dòng)映射行為:

// 獲取當(dāng)前ResultMap是否繼承了其他ResultMap
String extend = resultMapNode.getStringAttribute("extends");

// 獲取自動(dòng)映射標(biāo)志
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

然后解析類(lèi)型別名,嘗試獲取當(dāng)前resultMap對(duì)應(yīng)的java類(lèi)型:

// 解析出返回類(lèi)型
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
    // 嵌套映射時(shí),外部對(duì)象屬性類(lèi)型定義優(yōu)先級(jí)較低
    typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}

如果當(dāng)前resultMap沒(méi)有直接指定對(duì)應(yīng)的java類(lèi)型,mybatis會(huì)嘗試通過(guò)上下文來(lái)推斷出合適的類(lèi)型,負(fù)責(zé)推斷類(lèi)型的方法是inheritEnclosingType()方法:

protected Class<?> inheritEnclosingType(XNode resultMapNode, Class<?> enclosingType) {
    // 一對(duì)一集合映射
    if ("association".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
        // 通過(guò)屬性定義推斷java類(lèi)型
        String property = resultMapNode.getStringAttribute("property");
        if (property != null && enclosingType != null) {
            MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());
            return metaResultType.getSetterType(property);
        }
    } else if ("case".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
        // 鑒別器
        return enclosingType;
    }
    return null;
}

inheritEnclosingType()方法的實(shí)現(xiàn)并不復(fù)雜,首先他不會(huì)處理配置了resultMap屬性的元素,因?yàn)闊o(wú)論是association元素也好還是case元素也好,如果他們配置了resultMap屬性,那就意味著該元素對(duì)應(yīng)屬性的類(lèi)型轉(zhuǎn)換處理操作是由被引用的resultMap來(lái)處理的,當(dāng)前resultMap無(wú)需處理,因此,當(dāng)前方法也就無(wú)需進(jìn)行類(lèi)型推斷操作.

針對(duì)association元素,因?yàn)樵谠O(shè)計(jì)上association元素的作用就是為某一對(duì)象屬性配置一個(gè)復(fù)雜對(duì)象的映射,因此我們可以借助于屬性名稱(chēng)屬性所屬對(duì)象的類(lèi)型通過(guò)反射獲取到association元素所對(duì)應(yīng)的類(lèi)型定義.

而針對(duì)case元素,其父元素discriminatorjavaType屬性是必填的,這個(gè)屬性直接就表明了當(dāng)前鑒別器所對(duì)應(yīng)的java類(lèi)型,discriminatorjavaType屬性定義在解析時(shí)是作為enclosingType參數(shù)傳遞進(jìn)來(lái),因此從理論上來(lái)講直接返回enclosingType參數(shù)即可.

<!ATTLIST discriminator
column CDATA #IMPLIED
javaType CDATA #REQUIRED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
>

那么同為嵌套映射三巨頭collection為什么沒(méi)有進(jìn)行類(lèi)型推斷操作呢?

這是因?yàn)?code>collection元素用于配置集合映射,所以解析collection時(shí),enclosingType參數(shù)對(duì)應(yīng)的是一個(gè)集合類(lèi)型,根據(jù)mybatis現(xiàn)有配置,我們無(wú)法為集合指定泛型,因此除非用戶(hù)明確指出,否則我們無(wú)法通過(guò)反射或者其他方式獲取集合中元素的類(lèi)型.

在得到當(dāng)前resultMap元素對(duì)應(yīng)的java類(lèi)型之后,mybatis創(chuàng)建了一個(gè)用于存放ResultMapping對(duì)象的resultMappings集合:

// 返回結(jié)果定義
List<ResultMapping> resultMappings = new ArrayList<>();
// 添加所有額外的ResultMap集合
resultMappings.addAll(additionalResultMappings);

之后會(huì)依次將當(dāng)前resultMap的所有子元素全部轉(zhuǎn)換為ResultMapping對(duì)象,并保存到resultMappings集合中.

探究resultMap子元素的解析操作

resultMap的子元素轉(zhuǎn)換為ResultMapping對(duì)象的操作,根據(jù)子元素的類(lèi)型和作用,基本可以分為三類(lèi):構(gòu)造參數(shù)配置,鑒別器配置,以及標(biāo)準(zhǔn)屬性映射配置.

List<XNode> resultChildren = resultMapNode.getChildren();
// 開(kāi)始處理ResultMap中的每一個(gè)子節(jié)點(diǎn)
for (XNode resultChild : resultChildren) {
    // 獲取每一個(gè)ResultMap的子節(jié)點(diǎn) 處理constructor節(jié)點(diǎn),該節(jié)點(diǎn)用來(lái)配置構(gòu)造方法
    if ("constructor".equals(resultChild.getName())) {
        // 處理constructor節(jié)點(diǎn)
        processConstructorElement(resultChild, typeClass, resultMappings);

    } else if ("discriminator".equals(resultChild.getName())) {
        // 處理discriminator節(jié)點(diǎn)(鑒別器)
        // 通過(guò)配置discriminator節(jié)點(diǎn)可以實(shí)現(xiàn)根據(jù)查詢(xún)結(jié)果動(dòng)態(tài)生成查詢(xún)語(yǔ)句的功能
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);

    } else {
        // 獲取ID標(biāo)簽
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
            // 添加ID標(biāo)記
            flags.add(ResultFlag.ID);
        }

        // 添加ResultMapping配置
        resultMappings.add(
                buildResultMappingFromContext(
                        resultChild
                        , typeClass
                        , flags
                )
        );
    }
}

這三類(lèi)的解析操作則分別對(duì)應(yīng)著上面代碼中的三條分支語(yǔ)句.

解析標(biāo)準(zhǔn)屬性映射配置

其中最基本的的操作是標(biāo)準(zhǔn)屬性映射配置,它對(duì)應(yīng)的代碼塊是上面代碼中的最后一個(gè)else語(yǔ)句:

// 獲取ID標(biāo)簽
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
    // 添加ID標(biāo)記
    flags.add(ResultFlag.ID);
}

// 添加ResultMapping配置
resultMappings.add(
        buildResultMappingFromContext(
                resultChild
                , typeClass
                , flags
        )
);

這一部分代碼主要用來(lái)處理id,result,association以及collection四個(gè)元素.

方法實(shí)現(xiàn)比較簡(jiǎn)單,除了會(huì)針對(duì)id元素的配置單獨(dú)添加一個(gè)ResultFlag.ID標(biāo)記之外,剩下的操作都交給了buildResultMappingFromContext()方法來(lái)完成.

ResultFlag是一個(gè)枚舉對(duì)象,他有兩個(gè)實(shí)現(xiàn):IDCONSTRUCTOR,分別用來(lái)為當(dāng)前配置的元素添加ID和構(gòu)造參數(shù)標(biāo)識(shí).

public enum ResultFlag {
  ID, CONSTRUCTOR
}

解析構(gòu)造參數(shù)配置

構(gòu)造參數(shù)配置的處理邏輯和標(biāo)準(zhǔn)屬性映射配置的處理邏輯非常相似,它對(duì)應(yīng)著第一個(gè)if語(yǔ)句分支:

 // 獲取每一個(gè)ResultMap的子節(jié)點(diǎn) 處理constructor節(jié)點(diǎn),該節(jié)點(diǎn)用來(lái)配置構(gòu)造方法
if ("constructor".equals(resultChild.getName())) {
    // 處理constructor節(jié)點(diǎn)
    processConstructorElement(resultChild, typeClass, resultMappings);

}

processConstructorElement()方法中,mybatis讀取出constructor元素的所有argidArg子元素定義.

因?yàn)檫@兩個(gè)元素用于配置構(gòu)造參數(shù),所以需要為他們添加上ResultFlag.CONSTRUCTOR標(biāo)記,針對(duì)idArg還要額外增加ResultFlag.ID標(biāo)記.

添加完標(biāo)記之后,剩下的操作就和標(biāo)準(zhǔn)屬性映射配置的處理一樣了,殊途同歸,將剩余的操作都交給了buildResultMappingFromContext()方法來(lái)完成:

private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    // 獲取constructor所有的子節(jié)點(diǎn)
    List<XNode> argChildren = resultChild.getChildren();

    for (XNode argChild : argChildren) {
        List<ResultFlag> flags = new ArrayList<>();
        // 表示屬于構(gòu)造參數(shù)
        flags.add(ResultFlag.CONSTRUCTOR);
        if ("idArg".equals(argChild.getName())) {
            // 主鍵標(biāo)記
            flags.add(ResultFlag.ID);
        }
        resultMappings.add(
                buildResultMappingFromContext(
                        argChild /*constructor元素的idArg或者arg子元素*/
                        , resultType/*構(gòu)造方法對(duì)應(yīng)的java對(duì)象*/
                        , flags /*參數(shù)類(lèi)型標(biāo)記*/
                )
        );
    }
}

因此buildResultMappingFromContext()方法實(shí)際處理的是id,result,association,collection,arg以及idArg六個(gè)子元素.

簡(jiǎn)單理解buildResultMappingFromContext方法的入?yún)?/h2>

buildResultMappingFromContext方法是用來(lái)構(gòu)建ResultMapping對(duì)象實(shí)例的,他有三個(gè)參數(shù)content,resultType以及flags.

其中content是一個(gè)XNode對(duì)象實(shí)例,他表示一個(gè)resultMap元素的子元素,他可以是arg,idArg,collection,association,result以及id.

resultType參數(shù)則表示這個(gè)元素對(duì)應(yīng)的java類(lèi)型。

flags表示這個(gè)元素的性質(zhì),比如這個(gè)元素是不是一個(gè)構(gòu)造參數(shù),或者這個(gè)元素是不是一個(gè)數(shù)據(jù)庫(kù)主鍵。

buildResultMappingFromContext方法的解析操作

buildResultMappingFromContext()方法的實(shí)現(xiàn)談不上復(fù)雜與否,基本就是簡(jiǎn)單邏輯的堆砌,我們先總覽一下代碼實(shí)現(xiàn):

/**
    * 構(gòu)建resultMapping對(duì)象
    *
    * @param context    ResultMapping代碼塊
    * @param resultType 返回類(lèi)型
    * @param flags      參數(shù)類(lèi)型標(biāo)記(構(gòu)造?主鍵?)
    */
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
        // 如果當(dāng)前節(jié)點(diǎn)定義的是一個(gè)構(gòu)造參數(shù),那么讀取的是其name屬性(形參名稱(chēng))。
        property = context.getStringAttribute("name");
    } else {
        // 不是構(gòu)造參數(shù),讀取property屬性
        property = context.getStringAttribute("property");
    }

    // 對(duì)應(yīng)的JDBC列名稱(chēng)
    String column = context.getStringAttribute("column");
    // 對(duì)應(yīng)的java類(lèi)型
    String javaType = context.getStringAttribute("javaType");
    // 對(duì)應(yīng)的jdbc類(lèi)型
    String jdbcType = context.getStringAttribute("jdbcType");

    // 是否引用了其他select語(yǔ)句
    String nestedSelect = context.getStringAttribute("select");
    /*
        resultMap中可以包含association或者collection這種復(fù)合節(jié)點(diǎn),這些復(fù)合類(lèi)型可以使用外部定義的resultMap或者內(nèi)嵌的resultMap,
        因此針對(duì)這里的處理邏輯是:如果有resultMap就獲取,沒(méi)有則動(dòng)態(tài)生成一個(gè),動(dòng)態(tài)生成的resultMap的唯一標(biāo)志是基于XNode#getValueBasedIdentifier計(jì)算得來(lái)的。
        */
    String nestedResultMap = context.getStringAttribute(
            "resultMap",/*使用指定的resultMap*/
            processNestedResultMappings(context, Collections.<ResultMapping>emptyList(), resultType)/*這里表示默認(rèn)值,如果沒(méi)有則動(dòng)態(tài)生成一個(gè)ResultMap*/
    );

    // 默認(rèn)情況下,子對(duì)象僅在至少一個(gè)列映射到其屬性非空時(shí)才創(chuàng)建。
    // 通過(guò)對(duì)這個(gè)屬性指定非空的列將改變默認(rèn)行為,這樣做之后Mybatis將僅在這些列非空時(shí)才創(chuàng)建一個(gè)子對(duì)象。
    // 可以指定多個(gè)列名,使用逗號(hào)分隔。默認(rèn)值:未設(shè)置(unset)。
    String notNullColumn = context.getStringAttribute("notNullColumn");

    // 當(dāng)連接多表時(shí),你將不得不使用列別名來(lái)避免ResultSet中的重復(fù)列名。
    // 因此你可以指定columnPrefix映射列名到一個(gè)外部的結(jié)果集中。
    String columnPrefix = context.getStringAttribute("columnPrefix");

    // 類(lèi)型轉(zhuǎn)換處理器
    String typeHandler = context.getStringAttribute("typeHandler");

    // 獲取resultSet集合
    String resultSet = context.getStringAttribute("resultSet");

    // 標(biāo)識(shí)出包含foreign keys的列的名稱(chēng)
    String foreignColumn = context.getStringAttribute("foreignColumn");

    // 懶加載
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));

    // 解析java類(lèi)型
    Class<?> javaTypeClass = resolveClass(javaType);
    // 解析類(lèi)型處理器
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    // 解析出jdbc類(lèi)型
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);

    // 創(chuàng)建最終的resultMap
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}

buildResultMappingFromContext()方法首先會(huì)根據(jù)元素的不同使用不同的方法來(lái)獲取元素對(duì)應(yīng)的屬性名稱(chēng).

因?yàn)?code>idArg和arg兩個(gè)元素特殊的DTD定義,所以在獲取這兩個(gè)元素的屬性名稱(chēng)時(shí),不能通過(guò)property屬性,而是要通過(guò)name屬性:

String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
    // 如果當(dāng)前節(jié)點(diǎn)定義的是一個(gè)構(gòu)造參數(shù),那么讀取的是其name屬性(形參名稱(chēng))。
    property = context.getStringAttribute("name");
} else {
    // 不是構(gòu)造參數(shù),讀取property屬性
    property = context.getStringAttribute("property");
}

前面的文章中提到過(guò),argidArg元素的name屬性,對(duì)應(yīng)的是java構(gòu)造方法的形參名稱(chēng),基于形參名稱(chēng)來(lái)配置構(gòu)造參數(shù),我們就可以忽略掉具體的構(gòu)造參數(shù)的順序了。

// 對(duì)應(yīng)的JDBC列名稱(chēng)
String column = context.getStringAttribute("column");
// 對(duì)應(yīng)的java類(lèi)型
String javaType = context.getStringAttribute("javaType");
// 對(duì)應(yīng)的jdbc類(lèi)型
String jdbcType = context.getStringAttribute("jdbcType");

獲取java字段名稱(chēng)之后,buildResultMappingFromContext()方法會(huì)進(jìn)行一些基礎(chǔ)屬性的獲取工作,比如獲取對(duì)應(yīng)的java類(lèi)型,對(duì)應(yīng)的數(shù)據(jù)庫(kù)列名稱(chēng),對(duì)應(yīng)的數(shù)據(jù)庫(kù)類(lèi)型等等.

// 是否引用了其他select語(yǔ)句
String nestedSelect = context.getStringAttribute("select");

之后,會(huì)判斷當(dāng)前元素有沒(méi)有配置select屬性.

idArg,arg,association,collection這四個(gè)元素都可以配置select屬性,select屬性可以引用一個(gè)現(xiàn)有的映射聲明語(yǔ)句。

處理完select屬性之后,開(kāi)始處理resultMap屬性的配置.

/*
    resultMap中可以包含association或者collection這種復(fù)合節(jié)點(diǎn),這些復(fù)合類(lèi)型可以使用外部定義的resultMap或者內(nèi)嵌的resultMap,
    因此針對(duì)這里的處理邏輯是:如果有resultMap就獲取,沒(méi)有則動(dòng)態(tài)生成一個(gè),動(dòng)態(tài)生成的resultMap的唯一標(biāo)志是基于XNode#getValueBasedIdentifier計(jì)算得來(lái)的。
    */
String nestedResultMap = context.getStringAttribute(
        "resultMap",/*使用指定的resultMap*/
        processNestedResultMappings(context, Collections.<ResultMapping>emptyList(), resultType)/*這里表示默認(rèn)值,如果沒(méi)有則動(dòng)態(tài)生成一個(gè)ResultMap*/
);

雖然arg,idArg,collection以及association這四個(gè)元素都能夠配置resultMap屬性,但是argidArg只能引用現(xiàn)有的結(jié)果映射配置,而collection,association這兩個(gè)元素還能直接用來(lái)配置嵌套結(jié)果映射.

因此針對(duì)collectionassociation這兩個(gè)元素,還會(huì)調(diào)用processNestedResultMappings()方法解析嵌套的結(jié)果映射配置.

processNestedResultMappings()方法是用來(lái)解析嵌套映射三巨頭的,因此除了collectionassociation元素之外,case元素也能被該方法處理,他負(fù)責(zé)將嵌套結(jié)果映射配置解析成相應(yīng)的ResultMap對(duì)象,并返回ResultMap對(duì)象的全局引用ID.

/**
    * 處理嵌套的ResultMap,作用于處理association或者collection節(jié)點(diǎn)、
    * <p>
    * resultMap中可以包含association或者collection這種復(fù)合節(jié)點(diǎn),這些復(fù)合類(lèi)型可以使用外部定義的resultMap或者內(nèi)嵌的resultMap,
    * 因此針對(duì)這里的處理邏輯是:如果有resultMap就獲取,
    * 沒(méi)有則動(dòng)態(tài)生成一個(gè),動(dòng)態(tài)生成的resultMap的唯一標(biāo)志是基于XNode#getValueBasedIdentifier計(jì)算得來(lái)的。
    *
    * @param context        父級(jí)XML代碼塊
    * @param resultMappings 已有的resultMapping集合
    * @param enclosingType  返回類(lèi)型
    */
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) throws Exception {
    if ("association".equals(context.getName())
            || "collection".equals(context.getName())
            || "case".equals(context.getName())) {
        // association和collection property有select屬性,這里只處理非select參數(shù)的代碼塊
        /*
            * select可以指定另外一個(gè)映射語(yǔ)句的ID,加載這個(gè)屬性映射需要的復(fù)雜類(lèi)型。
            * 在列屬性中指定的列值將會(huì)被傳遞給目標(biāo)select語(yǔ)句中作為參數(shù)。
            */
        if (context.getStringAttribute("select") == null) {
            // 沒(méi)有指定select屬性
            validateCollection(context, enclosingType);
            // 解析嵌套的resultMap元素
            ResultMap resultMap =
                    resultMapElement(context, resultMappings, enclosingType);
            return resultMap.getId();
        }
    }
    return null;
}

processNestedResultMappings()方法不會(huì)處理指定了select屬性的元素,這是因?yàn)?strong>同一條屬性映射配置不能在指定select屬性的同時(shí)配置嵌套映射.

processNestedResultMappings()方法的實(shí)現(xiàn)并不復(fù)雜,在單獨(dú)對(duì)collection元素做了校驗(yàn)之后,就把創(chuàng)建嵌套結(jié)果映射的任務(wù)交給resultMapElement()方法完成,這時(shí)候,我們可以看到processNestedResultMappings()方法和resultMapElement()方法二者之間構(gòu)成了遞歸調(diào)用的關(guān)系:

@startuml
autonumber
participant "resultMapElement()" as rme
participant "buildResultMappingFromContext()" as brmfc
participant "processNestedResultMappings()" as pnrm

activate rme
opt 處理標(biāo)準(zhǔn)屬性映射配置
    rme -> brmfc ++ : 處理元素定義
        opt 未引用其他結(jié)果映射
            brmfc -> pnrm ++ : 處理嵌套結(jié)果映射
            opt 未配置select屬性
            == 開(kāi)始遞歸調(diào)用 ==
                pnrm -[#red]> rme ++ #red: 解析處理嵌套結(jié)果映射 <color:red> <b> [遞歸調(diào)用]
                return
            == 結(jié)束遞歸調(diào)用 ==
            end
        return
        end
    return
end

@enduml
遞歸圖示

processNestedResultMappings()方法為什么需要單獨(dú)校驗(yàn)collection元素呢?

我們需要先明確一點(diǎn),調(diào)用processNestedResultMappings()方法的前提是子元素沒(méi)有配置resultMap屬性,在這個(gè)前提下,association元素和case元素的類(lèi)型早就在前面的處理過(guò)程中加載或者推斷出來(lái)了.

類(lèi)型獲取

因此,此時(shí)只有collection元素才有可能無(wú)法獲取對(duì)應(yīng)的集合類(lèi)型.所以在真正解析collection元素之前,我們需要校驗(yàn)collection元素是否定義了對(duì)應(yīng)的java類(lèi)型.

validateCollection()方法實(shí)現(xiàn)比較簡(jiǎn)單,因?yàn)槿绻?code>collection元素配置了resultMap或者resultType屬性,mybatis是可以根據(jù)這兩個(gè)屬性間接得到集合類(lèi)型的,因此validateCollection()方法主要是校驗(yàn)在沒(méi)有配置這些屬性的時(shí)候,能否通過(guò)反射來(lái)獲取集合類(lèi)型:

/**
    * 驗(yàn)證集合
    *
    * @param context       XML代碼塊
    * @param enclosingType 返回類(lèi)型
    */
protected void validateCollection(XNode context, Class<?> enclosingType) {
    if ("collection".equals(context.getName()) /*處理Collection集合*/
            && context.getStringAttribute("resultMap") == null/*沒(méi)有定義resultMap*/
            && context.getStringAttribute("resultType") == null/*沒(méi)有定義resultType*/
    ) {
        // 解析collection內(nèi)部塊

        // 獲取將要返回類(lèi)型的類(lèi)型元數(shù)據(jù)
        MetaClass metaResultType = MetaClass.forClass(enclosingType, configuration.getReflectorFactory());

        /*獲取其property值,該值對(duì)應(yīng)返回類(lèi)的字段*/
        String property = context.getStringAttribute("property");
        if (!metaResultType.hasSetter(property)) {
            throw new BuilderException(
                    "Ambiguous collection type for property '" + property + "'. You must specify 'resultType' or 'resultMap'.");
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容