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

鑒于processNestedResultMappings()后面的實(shí)現(xiàn)遞歸調(diào)用了resultMapElement()方法,所以我們繼續(xù)回到buildResultMappingFromContext()方法的解析過程中來.

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

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

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

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

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

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

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

在完成了對resultMap的處理之后鲤嫡,接下來buildResultMappingFromContext()方法會依次獲取元素的notNullColumn,columnPrefix,typeHandler,resultSet,foreignColumn,fetchType屬性配置,并轉(zhuǎn)換成具體需要使用的類型。

這些屬性并不是全都存在于元素的屬性定義中,可能某一個(gè)元素只具有其中部分屬性定義,甚至完全不包含這幾個(gè)屬性定義.

上面幾個(gè)屬性的處理只是簡單的取值,相對來說值得注意的是懶加載屬性配置的實(shí)現(xiàn),在這里,我們看到結(jié)果映射的懶加載配置會覆蓋全局懶加載配置:

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

在得到上面這些屬性定義之后,mybatis就會將這些屬性傳遞給MapperBuilderAssistantbuildResultMapping()方法來完成一個(gè)ResultMapping對象的創(chuàng)建工作.

學(xué)到這里,我們先停止繼續(xù)解析buildResultMapping()方法的欲望,回頭來看一下鑒別器配置discriminator元素的解析操作.

鑒別器配置的解析處理

discriminator元素的解析操作由processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings)方法來完成:

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

processDiscriminatorElement()方法的實(shí)現(xiàn)并不復(fù)雜,mybatis解析discriminator元素時(shí)蔓姚,會依次獲取他對應(yīng)的column(字段名稱)躏嚎,javaType(java類型),jdbcType(jdbc類型),typeHandler(類型轉(zhuǎn)換處理器定義)屬性定義.

然后通過別名機(jī)制解析出來具體的java/jdbc/類型轉(zhuǎn)換處理器類型,再遍歷處理每一個(gè)case子元素的定義:

/**
    * 解析鑒別器
    *
    * @param context        鑒別器上下文
    * @param resultType     返回類型
    * @param resultMappings 已有的ResultMap集合
    */
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    // 獲取字段名稱
    String column = context.getStringAttribute("column");
    // 獲取java類型
    String javaType = context.getStringAttribute("javaType");
    // 獲取jdbc類型
    String jdbcType = context.getStringAttribute("jdbcType");
    // 獲取類型處理器
    String typeHandler = context.getStringAttribute("typeHandler");
    // 獲取真實(shí)的java類型
    Class<?> javaTypeClass = resolveClass(javaType);
    // 獲取真實(shí)的類型處理器
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    // 獲取真實(shí)的jdbc類型
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);

    // 處理鑒別器
    Map<String, String> discriminatorMap = new HashMap<>();
    for (XNode caseChild : context.getChildren()) {
        // 解析case代碼塊
        // 解析case代碼塊的value標(biāo)記
        String value = caseChild.getStringAttribute("value");
        // 解析case代碼塊的ResultMap標(biāo)記
        String resultMap = caseChild.getStringAttribute("resultMap" /*使用指定的resultMap*/
                , processNestedResultMappings(caseChild, resultMappings, resultType/*如果沒有指定resultMap诫肠,則動態(tài)生成ResultMap實(shí)例*/
                )
        );
        // 鑒別器存放值和resultMap的對應(yīng)關(guān)系
        discriminatorMap.put(value, resultMap);
    }
    // 構(gòu)造鑒別器
    return builderAssistant.buildDiscriminator(
            resultType /*返回類型*/
            , column /*對應(yīng)的字段*/
            , javaTypeClass /*字段類型*/
            , jdbcTypeEnum /*jdbc類型*/
            , typeHandlerClass/*類型轉(zhuǎn)換處理器*/
            , discriminatorMap /*鑒別器映射集合*/
    );
}

負(fù)責(zé)解析case元素的方法是processNestedResultMappings()方法,該方法我們在前面已經(jīng)講過了,他負(fù)責(zé)解析嵌套結(jié)果映射配置,并返回嵌套結(jié)果映射對應(yīng)的ResultMap對象的全局引用ID.

需要注意的是,在調(diào)用processNestedResultMappings()方法時(shí),傳入的resultMappings集合,該參數(shù)是從外部傳入的:

String resultMap = caseChild.getStringAttribute("resultMap" /*使用指定的resultMap*/
                    , processNestedResultMappings(caseChild, resultMappings, resultType/*如果沒有指定resultMap司澎,則動態(tài)生成ResultMap實(shí)例*/
                    )
            );

如果我們追根溯源,會發(fā)現(xiàn)該集合保存的是discriminator元素的同級元素所對應(yīng)的ResultMapping對象.

前面說過,根據(jù)DTD定義,為具有resultMap性質(zhì)的元素配置discriminator子元素時(shí),discriminator子元素必須聲明在元素的尾部:

resultMap性質(zhì)的元素

因此在解析具有resultMap性質(zhì)的元素時(shí),它的discriminator子元素一定是最后一個(gè)被解析的,所以上面方法調(diào)用傳入的resultMappings集合保存的就是具有resultMap性質(zhì)的元素的除discriminator子元素之外的所有子元素定義.

這個(gè)resultMappings集合對象,最后會在方法調(diào)用中傳入到resultMapElement()方法中,這也是為什么前面我們說:

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

這個(gè)參數(shù)到這里就對應(yīng)上了.

processDiscriminatorElement()方法中聲明了一個(gè)類型為Map<String, String>discriminatorMap集合,該集合存放的是case元素所匹配的數(shù)據(jù)值以及該值對應(yīng)的ResultMap對象的引用ID.

當(dāng)我們獲取到discriminator中每一個(gè)case子元素的定義之后,Mybatis就會委托映射器構(gòu)建助手MapperBuilderAssistantbuildDiscriminator()方法來生成Discriminator對象区赵。

Discriminator對應(yīng)著mybatis中的discriminator元素定義惭缰,他只有兩個(gè)參數(shù),一個(gè)參數(shù)用來存儲他對應(yīng)的ResultMapping對象笼才,另一個(gè)參數(shù)則存儲著discriminatorcase元素配置的鑒別器指定字段的值和resultMap的關(guān)聯(lián)關(guān)系漱受。

這是Discriminator的基本定義:

/**
 * 鑒別器,每一個(gè)discriminator節(jié)點(diǎn)都對應(yīng)一個(gè)鑒別器
 *
 * @author Clinton Begin
 */
public class Discriminator {

    /**
     * resultMap對象
     * 所有的<result>節(jié)點(diǎn)
     */
    private ResultMapping resultMapping;
    /**
     * 鑒別器指定字段的值和resultMap的關(guān)聯(lián)關(guān)系
     * if then 的映射
     */
    private Map<String, String> discriminatorMap;

    Discriminator() {
    }
    // ...省略...
}

在映射器構(gòu)建助手的buildDiscriminator()方法中首先會使用discriminator元素中配置的幾個(gè)屬性,生成對應(yīng)的ResultMapping對象骡送,具體的ResultMapping對象的生成過程昂羡,是由buildResultMapping方法來完成的,這個(gè)方法我們前面提到過,后面會統(tǒng)一介紹.

/**
    * 構(gòu)造Discriminator對象
    *
    * @param resultType       返回類型
    * @param column           列名稱
    * @param javaType         java類型
    * @param jdbcType         jdbc類型
    * @param typeHandler      類型轉(zhuǎn)換處理器
    * @param discriminatorMap 鑒別器指定字段的值和resultMap的關(guān)聯(lián)關(guān)系
    * @return Discriminator對象
    */
public Discriminator buildDiscriminator(
        Class<?> resultType,
        String column,
        Class<?> javaType,
        JdbcType jdbcType,
        Class<? extends TypeHandler<?>> typeHandler,
        Map<String, String> discriminatorMap) {
    // 構(gòu)建resultMap
    ResultMapping resultMapping = buildResultMapping(
            resultType,
            null,
            column,
            javaType,
            jdbcType,
            null,
            null,
            null,
            null,
            typeHandler,
            new ArrayList<ResultFlag>(),
            null,
            null,
            false);

    Map<String, String> namespaceDiscriminatorMap = new HashMap<>();

    // 循環(huán)處理所有的case元素的定義
    for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
        String resultMap = e.getValue();
        // 拼接命名空間
        resultMap = applyCurrentNamespace(resultMap, true);

        // 更新鑒別器和resultMap的關(guān)系
        namespaceDiscriminatorMap.put(e.getKey(), resultMap);
    }

    // 構(gòu)建鑒別器
    return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
}

在生成了discriminator對應(yīng)的ResultMapping對象之后摔踱,Mybatis會循環(huán)處理所有現(xiàn)有的鑒別器指定字段的值和resultMap的關(guān)聯(lián)關(guān)系虐先,這個(gè)處理操作主要是將現(xiàn)有的resultMap引用由局部改為全局。

完成這些操作之后派敷,mybatis就會使用這些數(shù)據(jù)來構(gòu)建一個(gè)Discriminator對象,負(fù)責(zé)創(chuàng)建Discriminator對象的構(gòu)建器Discriminator.Builder在實(shí)現(xiàn)上基本就是簡單的賦值操作:

public static class Builder {
    private Discriminator discriminator = new Discriminator();

    public Builder(Configuration configuration, ResultMapping resultMapping, Map<String, String> discriminatorMap) {
        discriminator.resultMapping = resultMapping;
        discriminator.discriminatorMap = discriminatorMap;
    }

    public Discriminator build() {
        assert discriminator.resultMapping != null;
        assert discriminator.discriminatorMap != null;
        assert !discriminator.discriminatorMap.isEmpty();
        //lock down map
        discriminator.discriminatorMap = Collections.unmodifiableMap(discriminator.discriminatorMap);
        return discriminator;
    }
}

回頭看buildResultMapping方法

ok,處理了關(guān)于鑒別器的解析過程之后,我們回過頭來繼續(xù)看負(fù)責(zé)創(chuàng)建ResultMapping對象的buildResultMapping()方法:

/**
    * 構(gòu)建ResultMapping實(shí)體
    *
    * @param resultType      返回類型
    * @param property        屬性名稱
    * @param column          字段名稱
    * @param javaType        java類型
    * @param jdbcType        jdbc類型
    * @param nestedSelect    嵌套的查詢語句
    * @param nestedResultMap 嵌套的resultMap
    * @param notNullColumn   非空字段
    * @param columnPrefix    列前綴
    * @param typeHandler     類型處理器
    * @param flags           屬性標(biāo)記
    * @param resultSet       多結(jié)果集定義
    * @param foreignColumn   父數(shù)據(jù)列名稱集合
    * @param lazy            懶加載標(biāo)記
    */
public ResultMapping buildResultMapping(
        Class<?> resultType,
        String property,
        String column,
        Class<?> javaType,
        JdbcType jdbcType,
        String nestedSelect,
        String nestedResultMap,
        String notNullColumn,
        String columnPrefix,
        Class<? extends TypeHandler<?>> typeHandler,
        List<ResultFlag> flags,
        String resultSet,
        String foreignColumn,
        boolean lazy) {
    // 推斷返回的java類型
    Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);

    // 解析類型處理器
    TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);

    // 解析混合列蛹批,在Mybatis中對于嵌套查詢撰洗,我們可以在定義column的時(shí)候,使用column= "{prop1=col1,prop2=col2}"
    // 這樣的語法來配置多個(gè)列名傳入到嵌套查詢語句中的名稱腐芍。其中prop1表示嵌套查詢中的參數(shù)名稱差导,col1表示主查詢中列名稱。
    List<ResultMapping> composites = parseCompositeColumnName(column);

    // 構(gòu)建ResultMapping
    return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
            .jdbcType(jdbcType)
            .nestedQueryId(applyCurrentNamespace(nestedSelect, true))/*處理嵌套查詢的ID*/
            .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))/*處理嵌套ResultMap的Id*/
            .resultSet(resultSet)
            .typeHandler(typeHandlerInstance)
            .flags(flags == null ? new ArrayList<ResultFlag>() : flags)
            .composites(composites) /*混合列*/
            .notNullColumns(parseMultipleColumnNames(notNullColumn))
            .columnPrefix(columnPrefix)
            .foreignColumn(foreignColumn)
            .lazy(lazy)
            .build();
}

buildResultMapping方法的參數(shù)很多,但是不要慌,因?yàn)樗慕馕稣娴暮芎唵?

buildResultMapping方法首先會借助resolveResultJavaType()resolveTypeHandler()方法解析出當(dāng)前ResultMapping對象對應(yīng)的java類型以及負(fù)責(zé)類型轉(zhuǎn)換的類型轉(zhuǎn)換處理器實(shí)例猪勇。

/**
    * 解析返回的java類型
    *
    * @param resultType 返回類型
    * @param property   字段名稱
    * @param javaType   java類型
    */
private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {

    if (javaType == null && property != null) {
        // 沒有javaType根據(jù)類的元數(shù)據(jù)集合獲取javaType
        try {
            MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
            javaType = metaResultType.getSetterType(property);
        } catch (Exception e) {
            //ignore, following null check statement will deal with the situation
        }
    }
    if (javaType == null) {
        javaType = Object.class;
    }
    return javaType;
}

resolveResultJavaType()是對前面代碼的一個(gè)補(bǔ)充,他可以通過反射操作來獲取ResultMapping對應(yīng)的javaType,如果無法通過反射獲取javaType,那就默認(rèn)賦值為Object.class.

resolveTypeHandler()方法負(fù)責(zé)實(shí)例化類型轉(zhuǎn)換處理器,操作很簡單,這里就不在贅述了:

 /**
    * 解析出指定的類型處理器
    *
    * @param javaType        java類型
    * @param typeHandlerType 類型處理器的類型
    * @return 類型處理器實(shí)例
    */
protected TypeHandler<?> resolveTypeHandler(Class<?> javaType, Class<? extends TypeHandler<?>> typeHandlerType) {
    if (typeHandlerType == null) {
        return null;
    }
    // javaType ignored for injected handlers see issue #746 for full detail
    TypeHandler<?> handler = typeHandlerRegistry.getMappingTypeHandler(typeHandlerType);
    if (handler == null) {
        // 創(chuàng)建一個(gè)新的類型處理器
        // not in registry, create a new one
        handler = typeHandlerRegistry.getInstance(javaType, typeHandlerType);
    }
    return handler;
}

完成這兩個(gè)類型的處理之后,mybatis針對column屬性值還會執(zhí)行一次特殊的處理,在介紹associationcollection元素的配置時(shí),提到column屬性可以是普通的列名稱定義设褐,比如column="id",也可以是一個(gè)復(fù)合的屬性描述,比如:column="{prop1=col1,prop2=col2}".

所以針對復(fù)合屬性描述,mybatis會通過parseCompositeColumnName()方法將其解析成一組ResultMapping定義:

private List<ResultMapping> parseCompositeColumnName(String columnName) {
    List<ResultMapping> composites = new ArrayList<>();
    if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
        // 以 【{}=,】 作為分隔符處理內(nèi)容
        StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
        while (parser.hasMoreTokens()) {
            // 獲取屬性名稱
            String property = parser.nextToken();
            // 獲取列名稱
            String column = parser.nextToken();

            ResultMapping complexResultMapping = new ResultMapping.Builder(
                    configuration /*Mybatis配置*/
                    , property/*屬性名稱*/
                    , column/*列名稱*/
                    , configuration.getTypeHandlerRegistry().getUnknownTypeHandler()/*Mybatis默認(rèn)的未知類型的轉(zhuǎn)換處理器*/
            ).build();

            composites.add(complexResultMapping);
        }
    }
    return composites;
}

這一組ResultMapping定義有別于常規(guī)意義上的ResultMapping,它配置的是嵌套查詢中,主查詢結(jié)果對象中屬性名稱和子查詢語句的參數(shù)關(guān)系.

最后buildResultMapping()方法就會通過前面處理好的屬性完成一個(gè)ResultMapping對象的創(chuàng)建工作:

// 構(gòu)建ResultMapping
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
        .jdbcType(jdbcType)
        .nestedQueryId(applyCurrentNamespace(nestedSelect, true))/*處理嵌套查詢的ID*/
        .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))/*處理嵌套ResultMap的Id*/
        .resultSet(resultSet)
        .typeHandler(typeHandlerInstance)
        .flags(flags == null ? new ArrayList<ResultFlag>() : flags)
        .composites(composites) /*混合列*/
        .notNullColumns(parseMultipleColumnNames(notNullColumn))
        .columnPrefix(columnPrefix)
        .foreignColumn(foreignColumn)
        .lazy(lazy)
        .build();

在上面的方法調(diào)用中,針對引用的嵌套查詢語句和嵌套映射,還提前做了一個(gè)局部ID轉(zhuǎn)全局ID的操作.

buildResultMapping()parseCompositeColumnName()兩個(gè)方法中,實(shí)際創(chuàng)建ResultMapping對象的工作都是由ResultMapping的構(gòu)建器ResultMapping.Builder來完成的.

ResultMapping的創(chuàng)建工作

都講到這里了,實(shí)在是避不開ResultMapping對象了,但是ResultMapping對象雖然看起來屬性很多,可這些屬性基本上咱們都做了一定的了解了,所以這個(gè)代碼我就隨手一貼,你就隨手一看,咱也不大費(fèi)周折的去看每一個(gè)屬性了:

public class ResultMapping {

    /**
     * 配置
     */
    private Configuration configuration;
    /**
     * 屬性名稱
     */
    private String property;
    /**
     * 對應(yīng)的列名稱
     */
    private String column;
    /**
     * java類型
     */
    private Class<?> javaType;
    /**
     * jdbc類型
     */
    private JdbcType jdbcType;
    /**
     * 類型處理器
     */
    private TypeHandler<?> typeHandler;
    /**
     * 內(nèi)部嵌套的或引用的ResultMap
     */
    private String nestedResultMapId;
    /**
     * 內(nèi)部嵌套的或引用的查詢語句
     */
    private String nestedQueryId;
    /**
     * 非空字段集合
     */
    private Set<String> notNullColumns;
    /**
     * 列名前綴
     */
    private String columnPrefix;
    /**
     * 返回類型標(biāo)記
     * 構(gòu)造參數(shù)泣刹,JDBC主鍵
     */
    private List<ResultFlag> flags;
    /**
     * resultMaps,嵌套的resultMap定義助析,是通過嵌套語句的column字段中以column={a=c,b=d}的方式定義出來的集合
     */
    private List<ResultMapping> composites;
    /**
     * resultSet
     */
    private String resultSet;

    /**
     * 外鍵
     */
    private String foreignColumn;

    /**
     * 懶加載標(biāo)記
     */
    private boolean lazy;

    ResultMapping() {
    }
    // ... 省略 ...
}

ResultMapping.Builder的工作流程并不復(fù)雜,他提供的方法除了構(gòu)造方法和build()方法之外基本都是簡單的屬性賦值操作.

構(gòu)造方法的實(shí)現(xiàn)也不復(fù)雜,在重載形式為Builder(Configuration configuration, String property)的構(gòu)造方法中,完成了部分屬性的初始化操作:

public Builder(Configuration configuration, String property) {
    resultMapping.configuration = configuration;
    resultMapping.property = property;
    resultMapping.flags = new ArrayList<>();
    resultMapping.composites = new ArrayList<>();
    resultMapping.lazy = configuration.isLazyLoadingEnabled();
}

這里面最特殊的一行應(yīng)該就是lazy屬性的初始化使用了全局懶加載配置.

build()方法的實(shí)現(xiàn)主要還是做了一些對屬性二次處理和校驗(yàn)的工作:

public ResultMapping build() {
    // lock down collections
    resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
    resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
    resolveTypeHandler();
    validate();
    return resultMapping;
}

比如將ResultMapping對象中一些集合類型的屬性置為不可變:

resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);

在沒有指定類型轉(zhuǎn)換處理器的前提下,根據(jù)javaType屬性推斷出可用的類型轉(zhuǎn)換處理器實(shí)例:

private void resolveTypeHandler() {
    if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
        Configuration configuration = resultMapping.configuration;

        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

        resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, resultMapping.jdbcType);
    }
}

以及對當(dāng)前ResultMapping對象的完整性進(jìn)行校驗(yàn):

private void validate() {
    // 在一個(gè)ResultMapping定義中不能同時(shí)引用nestedQueryId和nestedResultMapId
    // Issue #697: cannot define both nestedQueryId and nestedResultMapId
    if (resultMapping.nestedQueryId != null && resultMapping.nestedResultMapId != null) {
        throw new IllegalStateException("Cannot define both nestedQueryId and nestedResultMapId in property " + resultMapping.property);
    }
    // 沒有類型處理程序就不應(yīng)該有映射
    // Issue #5: there should be no mappings without typehandler
    if (resultMapping.nestedQueryId == null && resultMapping.nestedResultMapId == null && resultMapping.typeHandler == null) {
        throw new IllegalStateException("No typehandler found for property " + resultMapping.property);
    }
    // column 僅在嵌套的結(jié)果圖中可選,但在其余部分中不可選
    // Issue #4 and GH #39: column is optional only in nested resultmaps but not in the rest
    if (resultMapping.nestedResultMapId == null && resultMapping.column == null && resultMapping.composites.isEmpty()) {
        throw new IllegalStateException("Mapping is missing column attribute for property " + resultMapping.property);
    }
    // 屬性中應(yīng)該有相同數(shù)量的列和foreignColumns
    if (resultMapping.getResultSet() != null) {
        int numColumns = 0;
        if (resultMapping.column != null) {
            numColumns = resultMapping.column.split(",").length;
        }
        int numForeignColumns = 0;
        if (resultMapping.foreignColumn != null) {
            numForeignColumns = resultMapping.foreignColumn.split(",").length;
        }
        if (numColumns != numForeignColumns) {
            throw new IllegalStateException("There should be the same number of columns and foreignColumns in property " + resultMapping.property);
        }
    }
}

首先,一個(gè)ResultMapping對象是不能同時(shí)指定嵌套查詢和嵌套結(jié)果映射的.

// 在一個(gè)ResultMapping定義中不能同時(shí)引用nestedQueryId和nestedResultMapId
// Issue #697: cannot define both nestedQueryId and nestedResultMapId
if (resultMapping.nestedQueryId != null && resultMapping.nestedResultMapId != null) {
    throw new IllegalStateException("Cannot define both nestedQueryId and nestedResultMapId in property " + resultMapping.property);
}

其次,一個(gè)ResultMapping對象如果沒有指定嵌套查詢,也沒有指定嵌套結(jié)果映射,那么,他就應(yīng)該有一個(gè)可用的類型轉(zhuǎn)換處理器,不然,是沒辦法完成將數(shù)據(jù)庫數(shù)據(jù)轉(zhuǎn)換為對象的操作的.

// 沒有類型處理程序就不應(yīng)該有映射
// Issue #5: there should be no mappings without typehandler
if (resultMapping.nestedQueryId == null && resultMapping.nestedResultMapId == null && resultMapping.typeHandler == null) {
    throw new IllegalStateException("No typehandler found for property " + resultMapping.property);
}

同時(shí),如果一個(gè)ResultMapping對象沒有指定嵌套結(jié)果映射,那么就意味著這個(gè)ResultMapping對象必須指定了column屬性,否則,他無法完成屬性的映射或者執(zhí)行子查詢.

// column 僅在嵌套的結(jié)果圖中可選椅您,但在其余部分中不可選
// Issue #4 and GH #39: column is optional only in nested resultmaps but not in the rest
if (resultMapping.nestedResultMapId == null && resultMapping.column == null && resultMapping.composites.isEmpty()) {
    throw new IllegalStateException("Mapping is missing column attribute for property " + resultMapping.property);
}

最后,因?yàn)樵?code>多結(jié)果集模式下,column屬性將會配合著foreignColumn屬性一起使用,且foreignColumn屬性和column屬性之間是順序關(guān)聯(lián)的.

所以,foreignColumn屬性和column屬性所配置列的數(shù)量應(yīng)該是一致的.

// 屬性中應(yīng)該有相同數(shù)量的列和foreignColumns
if (resultMapping.getResultSet() != null) {
    int numColumns = 0;
    if (resultMapping.column != null) {
        numColumns = resultMapping.column.split(",").length;
    }
    int numForeignColumns = 0;
    if (resultMapping.foreignColumn != null) {
        numForeignColumns = resultMapping.foreignColumn.split(",").length;
    }
    if (numColumns != numForeignColumns) {
        throw new IllegalStateException("There should be the same number of columns and foreignColumns in property " + resultMapping.property);
    }
}

著手準(zhǔn)備構(gòu)建ResultMap對象

那么,到這里,我們就完成一個(gè)ResultMapping對象的創(chuàng)建工作,接下來,我們回過頭來去看,在得到ResultMapping集合之后,mybatis是如何創(chuàng)建ResultMap對象的.

現(xiàn)在我們回到resultMapElement()方法中,此時(shí)我們已經(jīng)完成了resultMap屬性及其子元素的解析工作.

接下來,mybatis就會利用我們獲取到這些數(shù)據(jù),構(gòu)建一個(gè)ResultMapResolver對象,來完成一個(gè)ResultMap對象的創(chuàng)建工作.

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

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

如果在構(gòu)建ResultMap對象的過程中觸發(fā)了IncompleteElementException異常,整個(gè)ResultMapResolver對象都會被存入到Configuration對象的incompleteResultMaps集合中,等待下次重試.

這個(gè)重試實(shí)現(xiàn)和緩存引用的處理邏輯基本一致,因?yàn)榭赡軙霈F(xiàn)跨mapper文件引用resultMap配置的場景,所以提供了該重試機(jī)制.

回頭看XMLMapperBuilderparse()方法,每解析一次mapper文件,都會嘗試重新解析出現(xiàn)解析異常的ResultMap對象:

重試

ResultMapResolver對象和CacheRefResolver對象很像,它緩存了創(chuàng)建一個(gè)ResultMap對象所需的所有數(shù)據(jù):

/**
 * ResultMap解析器
 *
 * @author Eduardo Macarron
 */
public class ResultMapResolver {
    /**
     * Mapper構(gòu)造助手
     */
    private final MapperBuilderAssistant assistant;
    /**
     * resultMap的唯一標(biāo)志
     */
    private final String id;
    /**
     * ResultMap的返回類型
     */
    private final Class<?> type;
    /**
     * 擴(kuò)展(繼承)的ResultMap集合
     */
    private final String extend;
    /**
     * 鑒別器
     */
    private final Discriminator discriminator;
    /**
     * 所有的resultMap子字段集合
     */
    private final List<ResultMapping> resultMappings;
    /**
     * 是否開啟自動映射
     */
    private final Boolean autoMapping;

    public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
        this.assistant = assistant;
        this.id = id;
        this.type = type;
        this.extend = extend;
        this.discriminator = discriminator;
        this.resultMappings = resultMappings;
        this.autoMapping = autoMapping;
    }

    /**
     * 解析并生成ResultMap
     *
     * @return resultMap
     */
    public ResultMap resolve() {
        return assistant.addResultMap(
                this.id
                , this.type
                , this.extend
                , this.discriminator
                , this.resultMappings
                , this.autoMapping
        );
    }

}

并在resolve()方法中將ResultMap對象的創(chuàng)建工作委托給MapperBuilderAssistant對象的addResultMap()方法來完成.

MapperBuilderAssistant對象的addResultMap()方法

addResultMap()方法的作用是創(chuàng)建一個(gè)ResultMap對象,并注冊到Configuration對象的ResultMap注冊表中,這個(gè)方法的實(shí)現(xiàn)代碼很長,但是也不復(fù)雜.

在實(shí)現(xiàn)上,他做的工作主要就是解析ResultMap對象的繼承關(guān)系,并合并具有繼承關(guān)系的兩個(gè)ResultMap對象的配置到子ResultMap對象中:

/**
    * 添加(注冊)一個(gè)ResultMap集合
    *
    * @param id             ResultMap唯一標(biāo)志
    * @param type           返回類型
    * @param extend         繼承的ResultMap
    * @param discriminator  鑒別器
    * @param resultMappings 現(xiàn)有的ResultMapping集合
    * @param autoMapping    是否自動處理類型轉(zhuǎn)換
    * @return ResultMap
    */
public ResultMap addResultMap(
        String id,
        Class<?> type,
        String extend,
        Discriminator discriminator,
        List<ResultMapping> resultMappings,
        Boolean autoMapping) {
    // 獲取命名空間標(biāo)志
    id = applyCurrentNamespace(id, false);
    // 繼承的命名空間
    extend = applyCurrentNamespace(extend, true);

    if (extend != null) {
        if (!configuration.hasResultMap(extend)) {
            // 不存在引用(繼承)的ResultMap,標(biāo)記為incomplete,待第二次處理
            throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
        }
        // 獲取被引入(繼承)的ResultMaps
        ResultMap resultMap = configuration.getResultMap(extend);
        // 獲取引入的resultMap的所有子節(jié)點(diǎn)配置
        List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
        // 本地覆蓋繼承
        extendedResultMappings.removeAll(resultMappings);
        // Remove parent constructor if this resultMap declares a constructor.
        // 當(dāng)前resultMap是否聲明了構(gòu)造函數(shù)
        boolean declaresConstructor = false;
        for (ResultMapping resultMapping : resultMappings) {
            if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                // 當(dāng)前resultMap聲明了構(gòu)造函數(shù)
                declaresConstructor = true;
                break;
            }
        }
        if (declaresConstructor) {
            // 如果已經(jīng)聲明了構(gòu)造函數(shù)襟沮,準(zhǔn)備移除父resultMap的構(gòu)造函數(shù)
            Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
            while (extendedResultMappingsIter.hasNext()) {
                if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                    // 移除被繼承的resultMap的構(gòu)造函數(shù)
                    extendedResultMappingsIter.remove();
                }
            }
        }
        //合并自身的resultMap以及繼承的resultMap的內(nèi)容,獲得最終的resultMap,這也意味著在啟動時(shí)就創(chuàng)建了完整的resultMap锥惋,
        // 這樣在運(yùn)行時(shí)就不需要去檢查繼承的映射和構(gòu)造器,有利于性能提升开伏。
        resultMappings.addAll(extendedResultMappings);
    }

    // 構(gòu)造ResultMap
    ResultMap resultMap = new ResultMap
            .Builder(
            configuration
            , id
            , type
            , resultMappings
            , autoMapping
    )
            .discriminator(discriminator)
            .build();

    // 注冊resultMap
    configuration.addResultMap(resultMap);
    return resultMap;
}

在實(shí)現(xiàn)上,首先addResultMap()方法會將當(dāng)前待處理的ResultMap和被繼承的ResultMapid通過applyCurrentNamespace()方法轉(zhuǎn)換為全局引用標(biāo)志,便于統(tǒng)一處理.

// 獲取命名空間標(biāo)志
id = applyCurrentNamespace(id, false);
// 繼承的命名空間
extend = applyCurrentNamespace(extend, true);

然后,如果當(dāng)前ResultMap對象存在繼承的ResultMap對象,就將父ResultMap對象中的配置合并到當(dāng)前ResultMap對象中.

在合并過程中,首先會校驗(yàn)被繼承的父ResultMap對象是否已經(jīng)配置到了Configuration中,如果沒有的話,將會拋出一個(gè)IncompleteElementException,中斷本次解析,等待重試.

// 獲取被引入(繼承)的ResultMaps
ResultMap resultMap = configuration.getResultMap(extend);
// 獲取引入的resultMap的所有子節(jié)點(diǎn)配置
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
// 本地覆蓋繼承
extendedResultMappings.removeAll(resultMappings);

在拿到父ResultMap對象之后,addResultMap()方法會移除所有在當(dāng)前ResultMap對象中定義的相同配置,

因?yàn)?code>ResultMapping對象重寫了equals()方法,因此具有相同屬性名稱(property)的ResultMapping對象會被認(rèn)為是相同的:

public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }

    ResultMapping that = (ResultMapping) o;

    if (property == null || !property.equals(that.property)) {
        return false;
    }

    return true;
}

如果父ResultMap對象還配置了構(gòu)造參數(shù),那么所有構(gòu)造參數(shù)對應(yīng)的配置都會被移除:

// 當(dāng)前resultMap是否聲明了構(gòu)造函數(shù)
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
    if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
        // 當(dāng)前resultMap聲明了構(gòu)造函數(shù)
        declaresConstructor = true;
        break;
    }
}
if (declaresConstructor) {
    // 如果已經(jīng)聲明了構(gòu)造函數(shù)膀跌,準(zhǔn)備移除父resultMap的構(gòu)造函數(shù)
    Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
    while (extendedResultMappingsIter.hasNext()) {
        if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            // 移除被繼承的resultMap的構(gòu)造函數(shù)
            extendedResultMappingsIter.remove();
        }
    }
}

最后將當(dāng)前ResultMap對象的配置和父ResultMap對象的配置合二為一:

//合并自身的resultMap以及繼承的resultMap的內(nèi)容,獲得最終的resultMap,這也意味著在啟動時(shí)就創(chuàng)建了完整的resultMap,
// 這樣在運(yùn)行時(shí)就不需要去檢查繼承的映射和構(gòu)造器固灵,有利于性能提升捅伤。
resultMappings.addAll(extendedResultMappings);

這樣就完成了ResultMap對象繼承關(guān)系的處理,然后就是通過ResultMap的構(gòu)建器來完成創(chuàng)建ResultMap對象的工作,并將得到的ResultMap對象注冊到Configuration中.

ResultMap對象

無論是創(chuàng)建ResultMap對象還是注冊ResultMap對象,這兩個(gè)操作都涉及到了一些額外的操作,為了能夠更好的理解ResultMap對象的創(chuàng)建和注冊行為,我們先簡單了解一下ResultMap對象.

public class ResultMap {
    /**
     * Mybatis配置對象
     */
    private Configuration configuration;
    /**
     * resultMap的唯一標(biāo)志
     */
    private String id;
    /**
     * resultMap的返回類型
     */
    private Class<?> type;
    /**
     * resultMap下的所有節(jié)點(diǎn)
     */
    private List<ResultMapping> resultMappings;
    /**
     * resultMap下的所有id節(jié)點(diǎn)
     */
    private List<ResultMapping> idResultMappings;
    /**
     * resultMap下的所有構(gòu)造器節(jié)點(diǎn)
     */
    private List<ResultMapping> constructorResultMappings;
    /**
     * resultMap下的所有普通屬性節(jié)點(diǎn)
     */
    private List<ResultMapping> propertyResultMappings;
    /**
     * 映射處理的數(shù)據(jù)列名集合
     */
    private Set<String> mappedColumns;
    /**
     * 映射的所有javaBean屬性名,包括ID巫玻,構(gòu)造器丛忆,普通屬性。
     */
    private Set<String> mappedProperties;
    /**
     * 鑒別器
     */
    private Discriminator discriminator;
    /**
     * 是否持有嵌套的resultMap,比如association或者collection,
     * 如果它包含discriminator,那么discriminator所持有的ResultMap對象的hasNestedResultMaps屬性會影響該屬性.
     */
    private boolean hasNestedResultMaps;
    /**
     * 是否有嵌套的查詢仍秤,比如select屬性
     */
    private boolean hasNestedQueries;
    /**
     * 自動映射熄诡,該屬性會覆蓋全局屬性
     */
    private Boolean autoMapping;

    private ResultMap() {
    }
    // ...省略...
}

在上面的代碼中,針對ResultMap的每個(gè)屬性都給出了注釋,如果有不了解的,在本文后面的內(nèi)容中,還會有更詳細(xì)的介紹.

創(chuàng)建ResultMap對象

簡單了解了ResultMap對象的定義之后,我們回頭繼續(xù)看在MapperBuilderAssistantaddResultMap()方法創(chuàng)建ResultMap對象的操作:

// 構(gòu)造ResultMap
    ResultMap resultMap = new ResultMap
            .Builder(
            configuration
            , id
            , type
            , resultMappings
            , autoMapping
    )
            .discriminator(discriminator)
            .build();

ResultMap.Builder是負(fù)責(zé)創(chuàng)建ResultMap對象的構(gòu)建器,在上面的方法調(diào)用鏈中,除了build()方法之外,所有的方法實(shí)現(xiàn)均是簡單的賦值操作.

在實(shí)現(xiàn)上與眾不同的build()方法責(zé)任重大,他完成了ResultMap對象部分?jǐn)?shù)據(jù)的初始化和校驗(yàn)工作.

build()方法的實(shí)現(xiàn)相對比較長,涉及到的屬性也比較多,我們先總覽一下代碼,然后我們再細(xì)看該方法的實(shí)現(xiàn):

public ResultMap build() {
    if (resultMap.id == null) {
        throw new IllegalArgumentException("ResultMaps must have an id");
    }
    resultMap.mappedColumns = new HashSet<>();
    resultMap.mappedProperties = new HashSet<>();
    resultMap.idResultMappings = new ArrayList<>();
    resultMap.constructorResultMappings = new ArrayList<>();
    resultMap.propertyResultMappings = new ArrayList<>();
    final List<String> constructorArgNames = new ArrayList<>();

    for (ResultMapping resultMapping : resultMap.resultMappings) {
        // 初始化是否含有嵌套查詢語句
        resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
        // 初始化是否含有嵌套resultMap
        resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
        // 獲取當(dāng)前列,包括復(fù)合列诗力,
        // 復(fù)合列是在org.apache.ibatis.builder.MapperBuilderAssistant.parseCompositeColumnName(String)中解析的凰浮。
        // 所有的數(shù)據(jù)庫列都被按順序添加到resultMap.mappedColumns中
        final String column = resultMapping.getColumn();

        if (column != null) {
            // 添加映射的列名稱
            resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
        } else if (resultMapping.isCompositeResult()) {
            // 當(dāng)前是符合列
            for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
                // 獲取復(fù)合列的列名稱
                final String compositeColumn = compositeResultMapping.getColumn();
                if (compositeColumn != null) {
                    // 添加映射的列名稱
                    resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
                }
            }
        }
        // 獲取javaBean的字段類型
        final String property = resultMapping.getProperty();
        if (property != null) {
            // 添加到映射的屬性集合中
            resultMap.mappedProperties.add(property);
        }
        // 如果本元素具有CONSTRUCTOR標(biāo)記,則添加到構(gòu)造函數(shù)參數(shù)列表,否則添加到普通屬性映射列表resultMap.propertyResultMappings
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            // 處理構(gòu)造函數(shù),注冊到當(dāng)前的構(gòu)造函數(shù)映射集合中
            resultMap.constructorResultMappings.add(resultMapping);
            if (resultMapping.getProperty() != null) {
                // 添加到構(gòu)造函數(shù)集合內(nèi)
                constructorArgNames.add(resultMapping.getProperty());
            }
        } else {
            // 不是構(gòu)造函數(shù),直接添加到普通屬性集合內(nèi)
            resultMap.propertyResultMappings.add(resultMapping);
        }

        // 如果當(dāng)前元素有ID標(biāo)記苇本,則添加到ID映射列表內(nèi)
        if (resultMapping.getFlags().contains(ResultFlag.ID)) {
            // 如果是ID標(biāo)志袜茧,添加到ID映射集合中
            resultMap.idResultMappings.add(resultMapping);
        }
    }

    // 循環(huán)結(jié)束

    // 如果當(dāng)前resultMap沒有聲明ID屬性,就把所有的屬性都作為ID屬性
    if (resultMap.idResultMappings.isEmpty()) {
        resultMap.idResultMappings.addAll(resultMap.resultMappings);
    }

    // 據(jù)聲明的構(gòu)造器參數(shù)名和類型,反射聲明的類,
    // 檢查其中是否包含對應(yīng)參數(shù)名和類型的構(gòu)造器,
    // 如果不存在匹配的構(gòu)造器,就拋出運(yùn)行時(shí)異常,這是為了確保運(yùn)行時(shí)不會出現(xiàn)異常
    if (!constructorArgNames.isEmpty()) {
        // 獲取構(gòu)造參數(shù)中的名稱集合
        final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
        if (actualArgNames == null) {
            throw new BuilderException("Error in result map '" + resultMap.id
                    + "'. Failed to find a constructor in '"
                    + resultMap.getType().getName() + "' by arg names " + constructorArgNames
                    + ". There might be more info in debug log.");
        }
        // 給resultMap的構(gòu)造器參數(shù)排序
        Collections.sort(resultMap.constructorResultMappings, (o1, o2) -> {
            int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
            int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
            return paramIdx1 - paramIdx2;
        });
    }
    // lock down collections
    // 為了避免resultMap的內(nèi)部結(jié)構(gòu)發(fā)生變更, 克隆一個(gè)不可修改的集合提供給用戶
    resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
    resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
    resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
    resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
    resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
    return resultMap;
}

首先build()方法對要創(chuàng)建的ResultMap對象的id屬性做了最基礎(chǔ)的校驗(yàn),因?yàn)?code>id屬性是mybatis操作ResultMap對象時(shí)的唯一憑據(jù).

if (resultMap.id == null) {
    throw new IllegalArgumentException("ResultMaps must have an id");
}

之后build()方法會循環(huán)處理所有的ResultMappings配置,并根據(jù)ResultMappings的配置來完成部分核心屬性的初始化工作.

比如,初始化當(dāng)前ResultMap對象中負(fù)責(zé)描述是否存在嵌套查詢語句和嵌套結(jié)果映射的hasNestedQuerieshasNestedResultMaps屬性:

// 初始化是否含有嵌套查詢語句
resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
// 初始化是否含有嵌套resultMap
resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);

以及初始化負(fù)責(zé)維護(hù)當(dāng)前ResultMap對象能夠處理哪些數(shù)據(jù)列的集合mappedColumns:

final String column = resultMapping.getColumn();

if (column != null) {
    // 添加映射的列名稱
    resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
} else if (resultMapping.isCompositeResult()) {
    // 當(dāng)前是符合列
    for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
        // 獲取復(fù)合列的列名稱
        final String compositeColumn = compositeResultMapping.getColumn();
        if (compositeColumn != null) {
            // 添加映射的列名稱
            resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
        }
    }
}

mappedColumns的取值有兩種,一種是用戶直接配置的簡單列名稱,一種用戶配置的復(fù)合的屬性描述中的數(shù)據(jù)列名稱.

還有初始化負(fù)責(zé)維護(hù)當(dāng)前ResultMap對象能夠處理哪些屬性的集合mappedProperties:

// 獲取javaBean的字段類型
final String property = resultMapping.getProperty();
if (property != null) {
    // 添加到映射的屬性集合中
    resultMap.mappedProperties.add(property);
}

最后,根據(jù)每個(gè)ResultMapping對象的標(biāo)記,是構(gòu)造參數(shù)配置的放入到維護(hù)構(gòu)造參數(shù)映射關(guān)系constructorResultMappings集合中,不是構(gòu)造參數(shù)的放入到維護(hù)普通屬性映射關(guān)系propertyResultMappings集合中.

// 如果本元素具有CONSTRUCTOR標(biāo)記,則添加到構(gòu)造函數(shù)參數(shù)列表,否則添加到普通屬性映射列表resultMap.propertyResultMappings
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
    // 處理構(gòu)造函數(shù)瓣窄,注冊到當(dāng)前的構(gòu)造函數(shù)映射集合中
    resultMap.constructorResultMappings.add(resultMapping);
    if (resultMapping.getProperty() != null) {
        // 添加到構(gòu)造函數(shù)集合內(nèi)
        constructorArgNames.add(resultMapping.getProperty());
    }
} else {
    // 不是構(gòu)造函數(shù),直接添加到普通屬性集合內(nèi)
    resultMap.propertyResultMappings.add(resultMapping);
}

而且針對構(gòu)造參數(shù)配置,如果指定了構(gòu)造參數(shù)的形參名稱,還會將該形參名稱放入到一個(gè)名為constructorArgNames的集合中,constructorArgNames是個(gè)局部變量,用于構(gòu)造方法的校驗(yàn)工作.

如果ResultMapping對象還持有ResultFlag.ID標(biāo)記,那么這個(gè)ResultMapping對象還會被放進(jìn)負(fù)責(zé)維護(hù)id屬性映射關(guān)系idResultMappings集合中.

// 如果當(dāng)前元素有ID標(biāo)記笛厦,則添加到ID映射列表內(nèi)
if (resultMapping.getFlags().contains(ResultFlag.ID)) {
    // 如果是ID標(biāo)志,添加到ID映射集合中
    resultMap.idResultMappings.add(resultMapping);
}

在循環(huán)處理完所有的ResultMappings配置之后,ResultMap對象屬性的初始化工作基本就完成了,但是針對idResultMappings集合,還會有一步額外的操作.

不知道你是否還記得我們在介紹id元素的時(shí)候,因?yàn)闉E用id元素造成的數(shù)據(jù)丟失問題?

相關(guān)文檔: Mybatis源碼之美:3.5.4.唯一標(biāo)標(biāo)識符--id元素

訪問地址: http://www.reibang.com/p/e51b125b8e6d

在那篇文章我們做了一個(gè)總結(jié):

id元素標(biāo)識的屬性將會作為對象的標(biāo)識符,該標(biāo)識符會在比較對象實(shí)例的時(shí)候被使用.

但是沒有說,如果沒有配置id元素,如何比較對象實(shí)例.

針對沒有配置id元素的場景,build()方法會把當(dāng)前ResultMap對象的所有ResultMapping配置放入到idResultMappings集合中,用來作為唯一標(biāo)識:

// 如果當(dāng)前resultMap沒有聲明ID屬性俺夕,就把所有的屬性都作為ID屬性
if (resultMap.idResultMappings.isEmpty()) {
    resultMap.idResultMappings.addAll(resultMap.resultMappings);
}

到這里,ResultMap對象屬性的初始化工作才算完成,接下來就是構(gòu)造方法的校驗(yàn)工作了,如果用戶配置構(gòu)造參數(shù)的時(shí)候指定了構(gòu)造參數(shù)的形參名稱,那么build()方法就會根據(jù)形參名稱去尋找相應(yīng)的構(gòu)造方法,并進(jìn)行基礎(chǔ)的校驗(yàn)工作:

// 據(jù)聲明的構(gòu)造器參數(shù)名和類型,反射聲明的類,
// 檢查其中是否包含對應(yīng)參數(shù)名和類型的構(gòu)造器,
// 如果不存在匹配的構(gòu)造器,就拋出運(yùn)行時(shí)異常,這是為了確保運(yùn)行時(shí)不會出現(xiàn)異常
if (!constructorArgNames.isEmpty()) {
    // 獲取構(gòu)造參數(shù)中的名稱集合
    final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
    if (actualArgNames == null) {
        throw new BuilderException("Error in result map '" + resultMap.id
                + "'. Failed to find a constructor in '"
                + resultMap.getType().getName() + "' by arg names " + constructorArgNames
                + ". There might be more info in debug log.");
    }
    // 給resultMap的構(gòu)造器參數(shù)排序
    Collections.sort(resultMap.constructorResultMappings, (o1, o2) -> {
        int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
        int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
        return paramIdx1 - paramIdx2;
    });
}

Mybatis源碼之美:3.5.5.配置構(gòu)造方法的constructor元素一文中,我們講從版本3.4.3開始,mybatis開始支持根據(jù)參數(shù)名稱匹配所對應(yīng)的構(gòu)造方法,這里就是對這一特性的處理和校驗(yàn).

argNamesOfMatchingConstructor()方法負(fù)責(zé)根據(jù)現(xiàn)有的constructorArgNames形參名稱集合,來尋找相匹配的第一個(gè)構(gòu)造方法,并返回匹配構(gòu)造方法的有序形參名稱集合.

因?yàn)樵摲椒ǖ慕馕錾婕暗揭恍╊~外的操作,所以我們待會再補(bǔ)充該方法的實(shí)現(xiàn)細(xì)節(jié),現(xiàn)在讓我們先完成build()方法的后續(xù)操作.

當(dāng)build()方法得到有序形參名稱集合之后,會利用該集合對現(xiàn)有的constructorResultMappings集合進(jìn)行排序,這樣constructorResultMappings集合中存放的配置就和實(shí)際的構(gòu)造方法順序?qū)?yīng)上了.

最后,build()方法借助于CollectionsunmodifiableList()方法將上面配置的這些集合轉(zhuǎn)換為不可變更的集合,至此就完成了一個(gè)ResultMap對象的創(chuàng)建工作了.

// lock down collections
// 為了避免resultMap的內(nèi)部結(jié)構(gòu)發(fā)生變更, 克隆一個(gè)不可修改的集合提供給用戶
resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
return resultMap;

argNamesOfMatchingConstructor()方法

現(xiàn)在我們可以回頭看一下argNamesOfMatchingConstructor()方法的實(shí)現(xiàn)了.

這個(gè)方法的實(shí)現(xiàn)邏輯是這樣的,先獲取返回對象類型的所有構(gòu)造方法,然后篩選出構(gòu)造參數(shù)數(shù)量和現(xiàn)有constructorArgNames中維護(hù)的形參名稱數(shù)量一致的構(gòu)造方法.

private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
    // 獲取resultMap對應(yīng)的javabean的構(gòu)造函數(shù)集合
    Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();

    for (Constructor<?> constructor : constructors) {
        // 獲取當(dāng)前構(gòu)造函數(shù)的入?yún)⒘斜?        Class<?>[] paramTypes = constructor.getParameterTypes();
        // 處理參數(shù)列表和當(dāng)前入?yún)?shù)量一致的構(gòu)造函數(shù)
        if (constructorArgNames.size() == paramTypes.length) {
            // 獲取構(gòu)造參數(shù)的入?yún)⒚Q集合(有序)
            List<String> paramNames = getArgNames(constructor);

            if (constructorArgNames.containsAll(paramNames)  /*參數(shù)名稱一致*/
                    && argTypesMatch(
                    constructorArgNames
                    , paramTypes /*真正的參數(shù)類型集合*/
                    , paramNames/*真正的參數(shù)名稱集合*/
            )/*類型是否一致*/
            ) {
                return paramNames;
            }
        }
    }
    return null;
}

然后用匹配到的構(gòu)造方法的參數(shù)類型和形參名稱,去一一對應(yīng)用戶配置的構(gòu)造參數(shù)名稱和類型,如果能夠匹配,則表示該構(gòu)造方法是一個(gè)有效的構(gòu)造方法,返回該構(gòu)造方法的形參名稱集合即可.

// 獲取構(gòu)造參數(shù)的入?yún)⒚Q集合(有序)
List<String> paramNames = getArgNames(constructor);

if (constructorArgNames.containsAll(paramNames)  /*參數(shù)名稱一致*/
        && argTypesMatch(
        constructorArgNames
        , paramTypes /*真正的參數(shù)類型集合*/
        , paramNames/*真正的參數(shù)名稱集合*/
)/*類型是否一致*/
) {
    return paramNames;
}

負(fù)責(zé)獲取構(gòu)造方法形參名稱的getArgNames()方法的實(shí)現(xiàn)別有洞天:

private List<String> getArgNames(Constructor<?> constructor) {
    List<String> paramNames = new ArrayList<>();
    List<String> actualParamNames = null;
    // 獲取參數(shù)列表中的注解集合,每一個(gè)構(gòu)造參數(shù)都對應(yīng)一個(gè)Annotation數(shù)組裳凸。
    final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();

    /*
        * 構(gòu)造參數(shù)的數(shù)量
        */
    int paramCount = paramAnnotations.length;

    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        // 處理每一個(gè)構(gòu)造參數(shù)
        String name = null;
        for (Annotation annotation : paramAnnotations[paramIndex]) {
            // 尋找當(dāng)前構(gòu)造參數(shù)上的Param注解
            if (annotation instanceof Param) {
                name = ((Param) annotation).value();
                break;
            }
        }
        if (name == null && resultMap.configuration.isUseActualParamName()) {
            // 如果沒有添加Param注解贱鄙,同時(shí)還開啟了使用真實(shí)參數(shù)的功能的話,則使用真實(shí)參數(shù)名稱
            if (actualParamNames == null) {
                //獲取構(gòu)造參數(shù)的所有入?yún)⒌膮?shù)名稱集合
                actualParamNames = ParamNameUtil.getParamNames(constructor);
            }
            if (actualParamNames.size() > paramIndex) {

                name = actualParamNames.get(paramIndex);
            }
        }
        // 添加參數(shù)名稱姨谷,如果沒有找到名稱的話贰逾,則使用arg+參數(shù)索引
        paramNames.add(name != null ? name : "arg" + paramIndex);
    }
    return paramNames;
}

在所有配置形參名稱的方案中,通過@Param注解配置的屬性名優(yōu)先級最高,開啟了useActualParamName特性下的真實(shí)形參名稱略低,保底的形參名稱則是argN,其中N表示形參索引.

負(fù)責(zé)獲取真實(shí)形參名稱ParamNameUtilgetParamNames()方法實(shí)現(xiàn)比較簡單,經(jīng)過一次跳轉(zhuǎn),該方法最終是借助于反射機(jī)制來完成的形參名稱獲取操作.

/**
    * 獲取指定方法的所有入?yún)⒌膮?shù)名稱集合
    *
    * @param executable 方法(Executable表示普通方法和構(gòu)造方法的通用父類)
    * @return 指定方法的所有入?yún)⒌膮?shù)名稱集合
    */
private static List<String> getParameterNames(Executable executable) {
    final List<String> names = new ArrayList<>();
    // 獲取方法所有入?yún)?    final Parameter[] params = executable.getParameters();
    for (Parameter param : params) {
        // 添加參數(shù)名稱
        names.add(param.getName());
    }
    // 返回
    return names;
}

argNamesOfMatchingConstructor方法中,除了getArgNames()方法之外,還有一個(gè)argTypesMatch()方法,該方法用來校驗(yàn)指定構(gòu)造方法的形參名稱和類型,能否和用戶配置的形參和類型對應(yīng)上:

/**
    * 匹配構(gòu)造參數(shù)的類型
    *
    * @param constructorArgNames 構(gòu)造函數(shù)的參數(shù)名稱集合
    * @param paramTypes          參數(shù)類型
    * @param paramNames          參數(shù)名稱
    * @return 是否匹配
    */
private boolean argTypesMatch(final List<String> constructorArgNames,
                                Class<?>[] paramTypes, List<String> paramNames) {
    for (int i = 0; i < constructorArgNames.size(); i++) {
        // 處理每一個(gè)構(gòu)造參數(shù)

        // 獲取參數(shù)類型
        Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
        // 獲取構(gòu)造函數(shù)的參數(shù)類型
        Class<?> specifiedType = resultMap.constructorResultMappings.get(i).getJavaType();

        if (!actualType.equals(specifiedType)) {
            // 判斷二者是否一致
            if (log.isDebugEnabled()) {
                log.debug("While building result map '" + resultMap.id
                        + "', found a constructor with arg names " + constructorArgNames
                        + ", but the type of '" + constructorArgNames.get(i)
                        + "' did not match. Specified: [" + specifiedType.getName() + "] Declared: ["
                        + actualType.getName() + "]");
            }
            return false;
        }
    }
    return true;
}

至此,構(gòu)建ResultMap對象涉及到的方法就已經(jīng)了解完畢了,現(xiàn)在我們回過頭去看,向Configuration對象注冊ResultMap對象時(shí)又執(zhí)行了那些額外操作?

注冊ResultMap對象

Configuration對象的addResultMap()方法用于注冊ResultMap對象,該方法的實(shí)現(xiàn)除了會將ResultMap對象存入到resultMaps集合中,還會執(zhí)行以下額外的操作:

public void addResultMap(ResultMap rm) {
    resultMaps.put(rm.getId(), rm);
    /*檢查當(dāng)前resultMap內(nèi)的鑒別器是否嵌套ResultMap*/
    checkLocallyForDiscriminatedNestedResultMaps(rm);
    /*檢查所有的ResultMap內(nèi)的鑒別器是否嵌套ResultMap*/
    checkGloballyForDiscriminatedNestedResultMaps(rm);
}

其中checkLocallyForDiscriminatedNestedResultMaps()方法用于檢測當(dāng)前ResultMap對象中是否配置了Discriminator,以及Discriminator中是否包含嵌套結(jié)果映射.

// Slow but a one time cost. A better solution is welcome.
protected void checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm) {
    if (!rm.hasNestedResultMaps() && rm.getDiscriminator() != null) {
        // 當(dāng)前ResultMap不含有嵌套的ResultMap,同時(shí)含有鑒別器
        for (Map.Entry<String, String> entry : rm.getDiscriminator().getDiscriminatorMap().entrySet()) {
            // 獲取鑒別器對應(yīng)的ResultMap
            String discriminatedResultMapName = entry.getValue();

            // 已經(jīng)加載了鑒別器對應(yīng)的ResultMap
            if (hasResultMap(discriminatedResultMapName)) {
                // 獲取鑒別器對應(yīng)的ResultMap
                ResultMap discriminatedResultMap = resultMaps.get(discriminatedResultMapName);

                if (discriminatedResultMap.hasNestedResultMaps()) {
                    // 更新ResultMap的hasNestedResultMaps字段為true.
                    rm.forceNestedResultMaps();
                    break;
                }
            }
        }
    }
}

如果當(dāng)前ResultMap對象中配置了Discriminator,且Discriminator中包含嵌套結(jié)果映射,那么就意味著當(dāng)前ResultMap對象也包含了嵌套結(jié)果映射.

畢竟ResultMap包含了Discriminator,Discriminator包含了嵌套結(jié)果映射,所以ResultMap包含嵌套結(jié)果映射,這個(gè)邏輯沒什么問題.

但是,有一點(diǎn)請注意,在判斷Discriminator中是否包含嵌套結(jié)果映射時(shí),有一個(gè)前置條件是被引用的嵌套結(jié)果映射必須已經(jīng)存在于當(dāng)前Configuration對象中:

// ... 省略 ...
if (hasResultMap(discriminatedResultMapName)) {
// ... 處理嵌套結(jié)果映射 ...
}
// ... 省略 ...

這是因?yàn)?前面我們構(gòu)建Discriminator對象時(shí),在解析case元素的resultMap屬性后,沒有進(jìn)行任何校驗(yàn):

// 解析case代碼塊的ResultMap標(biāo)記
String resultMap = caseChild.getStringAttribute("resultMap" /*使用指定的resultMap*/
        , processNestedResultMappings(caseChild, resultMappings, resultType/*如果沒有指定resultMap菠秒,則動態(tài)生成ResultMap實(shí)例*/
        )
);

因此,通過case元素的resultMap屬性引用的ResultMap對象此時(shí)可能還沒有初始化.

如果被引用的ResultMap對象還沒有初始化,checkLocallyForDiscriminatedNestedResultMaps()方法就無法得知被引用的ResultMap對象中是否包含嵌套結(jié)果映射,因此也就沒有辦法更新當(dāng)前ResultMap對象中的hasNestedResultMaps標(biāo)記.

checkGloballyForDiscriminatedNestedResultMaps()方法是對checkLocallyForDiscriminatedNestedResultMaps()方法在這種特殊場景下的一個(gè)補(bǔ)充.

protected void checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm) {
    if (rm.hasNestedResultMaps()) {
        // 當(dāng)前的ResultMap有嵌套ResultMap
        for (Map.Entry<String, ResultMap> entry : resultMaps.entrySet()) {
            // 遍歷處理全局已加載的resultMap
            Object value = entry.getValue();
            if (value instanceof ResultMap) {
                ResultMap entryResultMap = (ResultMap) value;
                if (!entryResultMap.hasNestedResultMaps() && entryResultMap.getDiscriminator() != null) {
                    // 已經(jīng)加載了鑒別器對應(yīng)的ResultMap
                    Collection<String> discriminatedResultMapNames = entryResultMap.getDiscriminator().getDiscriminatorMap().values();
                    // 獲取鑒別器引用了當(dāng)前的resultMap
                    if (discriminatedResultMapNames.contains(rm.getId())) {
                        // 更新entryResultMap的hasNestedResultMaps字段為true.
                        entryResultMap.forceNestedResultMaps();
                    }
                }
            }
        }
    }
}

當(dāng)注冊了一個(gè)包含嵌套結(jié)果映射的ResultMap對象時(shí),checkGloballyForDiscriminatedNestedResultMaps()方法就會獲取所有通過鑒別器引用了當(dāng)前ResultMap對象的ResultMap對象,并更新其hasNestedResultMaps標(biāo)記.

到這里,我們就完整的了解了創(chuàng)建和注冊ResultMap對象的流程.

寫在最后

這篇文章相對比較復(fù)雜,涉及到的內(nèi)容也比較多,因?yàn)檫壿嫳容^復(fù)雜,涉及到的代碼層級也比較深,因此建議配合著源碼多看幾遍.

加油!

就醬,告辭!

這條 gai 最靚的仔
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市氯迂,隨后出現(xiàn)的幾起案子践叠,更是在濱河造成了極大的恐慌,老刑警劉巖嚼蚀,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禁灼,死亡現(xiàn)場離奇詭異,居然都是意外死亡轿曙,警方通過查閱死者的電腦和手機(jī)弄捕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來导帝,“玉大人守谓,你說我怎么就攤上這事∧ィ” “怎么了斋荞?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虐秦。 經(jīng)常有香客問我平酿,道長,這世上最難降的妖魔是什么悦陋? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任蜈彼,我火速辦了婚禮,結(jié)果婚禮上俺驶,老公的妹妹穿的比我還像新娘幸逆。我一直安慰自己,他們只是感情好痒钝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布秉颗。 她就那樣靜靜地躺著,像睡著了一般送矩。 火紅的嫁衣襯著肌膚如雪蚕甥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天栋荸,我揣著相機(jī)與錄音菇怀,去河邊找鬼凭舶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛爱沟,可吹牛的內(nèi)容都是我干的帅霜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼呼伸,長吁一口氣:“原來是場噩夢啊……” “哼身冀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起括享,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤搂根,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后铃辖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剩愧,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年娇斩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仁卷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡犬第,死狀恐怖锦积,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瓶殃,我是刑警寧澤充包,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站遥椿,受9級特大地震影響基矮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜冠场,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一家浇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碴裙,春花似錦钢悲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至载慈,卻和暖如春惭等,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背办铡。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工辞做, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琳要,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓秤茅,卻偏偏與公主長得像稚补,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子框喳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

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