Mybatis源码之美Mybatis

Mybatis源码之美:3.5.6.resultMap元素的解析

2020-04-24  本文已影响0人  吃竹子的程序熊

鉴于processNestedResultMappings()后面的实现递归调用了resultMapElement()方法,所以我们继续回到buildResultMappingFromContext()方法的解析过程中来.

// 默认情况下,子对象仅在至少一个列映射到其属性非空时才创建。
// 通过对这个属性指定非空的列将改变默认行为,这样做之后Mybatis将仅在这些列非空时才创建一个子对象。
// 可以指定多个列名,使用逗号分隔。默认值:未设置(unset)。
String notNullColumn = context.getStringAttribute("notNullColumn");

// 当连接多表时,你将不得不使用列别名来避免ResultSet中的重复列名。
// 因此你可以指定columnPrefix映射列名到一个外部的结果集中。
String columnPrefix = context.getStringAttribute("columnPrefix");

// 类型转换处理器
String typeHandler = context.getStringAttribute("typeHandler");

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

// 标识出包含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属性配置,并转换成具体需要使用的类型。

这些属性并不是全都存在于元素的属性定义中,可能某一个元素只具有其中部分属性定义,甚至完全不包含这几个属性定义.

上面几个属性的处理只是简单的取值,相对来说值得注意的是懒加载属性配置的实现,在这里,我们看到结果映射的懒加载配置会覆盖全局懒加载配置:

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

在得到上面这些属性定义之后,mybatis就会将这些属性传递给MapperBuilderAssistantbuildResultMapping()方法来完成一个ResultMapping对象的创建工作.

学到这里,我们先停止继续解析buildResultMapping()方法的欲望,回头来看一下鉴别器配置discriminator元素的解析操作.

鉴别器配置的解析处理

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

if ("discriminator".equals(resultChild.getName())) {
    // 处理discriminator节点(鉴别器)
    // 通过配置discriminator节点可以实现根据查询结果动态生成查询语句的功能
    discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
}

processDiscriminatorElement()方法的实现并不复杂,mybatis解析discriminator元素时,会依次获取他对应的column(字段名称),javaType(java类型),jdbcType(jdbc类型),typeHandler(类型转换处理器定义)属性定义.

然后通过别名机制解析出来具体的java/jdbc/类型转换处理器类型,再遍历处理每一个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");
    // 获取真实的java类型
    Class<?> javaTypeClass = resolveClass(javaType);
    // 获取真实的类型处理器
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    // 获取真实的jdbc类型
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);

    // 处理鉴别器
    Map<String, String> discriminatorMap = new HashMap<>();
    for (XNode caseChild : context.getChildren()) {
        // 解析case代码块
        // 解析case代码块的value标记
        String value = caseChild.getStringAttribute("value");
        // 解析case代码块的ResultMap标记
        String resultMap = caseChild.getStringAttribute("resultMap" /*使用指定的resultMap*/
                , processNestedResultMappings(caseChild, resultMappings, resultType/*如果没有指定resultMap,则动态生成ResultMap实例*/
                )
        );
        // 鉴别器存放值和resultMap的对应关系
        discriminatorMap.put(value, resultMap);
    }
    // 构造鉴别器
    return builderAssistant.buildDiscriminator(
            resultType /*返回类型*/
            , column /*对应的字段*/
            , javaTypeClass /*字段类型*/
            , jdbcTypeEnum /*jdbc类型*/
            , typeHandlerClass/*类型转换处理器*/
            , discriminatorMap /*鉴别器映射集合*/
    );
}

负责解析case元素的方法是processNestedResultMappings()方法,该方法我们在前面已经讲过了,他负责解析嵌套结果映射配置,并返回嵌套结果映射对应的ResultMap对象的全局引用ID.

需要注意的是,在调用processNestedResultMappings()方法时,传入的resultMappings集合,该参数是从外部传入的:

String resultMap = caseChild.getStringAttribute("resultMap" /*使用指定的resultMap*/
                    , processNestedResultMappings(caseChild, resultMappings, resultType/*如果没有指定resultMap,则动态生成ResultMap实例*/
                    )
            );

如果我们追根溯源,会发现该集合保存的是discriminator元素的同级元素所对应的ResultMapping对象.

前面说过,根据DTD定义,为具有resultMap性质的元素配置discriminator子元素时,discriminator子元素必须声明在元素的尾部:

resultMap性质的元素

因此在解析具有resultMap性质的元素时,它的discriminator子元素一定是最后一个被解析的,所以上面方法调用传入的resultMappings集合保存的就是具有resultMap性质的元素的除discriminator子元素之外的所有子元素定义.

这个resultMappings集合对象,最后会在方法调用中传入到resultMapElement()方法中,这也是为什么前面我们说:

additionalResultMappings表示现有的ResultMapping集合,该参数只有在解析discriminator元素时才有数据,其他时候均为空集合.

这个参数到这里就对应上了.

processDiscriminatorElement()方法中声明了一个类型为Map<String, String>discriminatorMap集合,该集合存放的是case元素所匹配的数据值以及该值对应的ResultMap对象的引用ID.

当我们获取到discriminator中每一个case子元素的定义之后,Mybatis就会委托映射器构建助手MapperBuilderAssistantbuildDiscriminator()方法来生成Discriminator对象。

Discriminator对应着mybatis中的discriminator元素定义,他只有两个参数,一个参数用来存储他对应的ResultMapping对象,另一个参数则存储着discriminatorcase元素配置的鉴别器指定字段的值和resultMap的关联关系

这是Discriminator的基本定义:

/**
 * 鉴别器,每一个discriminator节点都对应一个鉴别器
 *
 * @author Clinton Begin
 */
public class Discriminator {

    /**
     * resultMap对象
     * 所有的<result>节点
     */
    private ResultMapping resultMapping;
    /**
     * 鉴别器指定字段的值和resultMap的关联关系
     * if then 的映射
     */
    private Map<String, String> discriminatorMap;

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

在映射器构建助手的buildDiscriminator()方法中首先会使用discriminator元素中配置的几个属性,生成对应的ResultMapping对象,具体的ResultMapping对象的生成过程,是由buildResultMapping方法来完成的,这个方法我们前面提到过,后面会统一介绍.

/**
    * 构造Discriminator对象
    *
    * @param resultType       返回类型
    * @param column           列名称
    * @param javaType         java类型
    * @param jdbcType         jdbc类型
    * @param typeHandler      类型转换处理器
    * @param discriminatorMap 鉴别器指定字段的值和resultMap的关联关系
    * @return Discriminator对象
    */
public Discriminator buildDiscriminator(
        Class<?> resultType,
        String column,
        Class<?> javaType,
        JdbcType jdbcType,
        Class<? extends TypeHandler<?>> typeHandler,
        Map<String, String> discriminatorMap) {
    // 构建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<>();

    // 循环处理所有的case元素的定义
    for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
        String resultMap = e.getValue();
        // 拼接命名空间
        resultMap = applyCurrentNamespace(resultMap, true);

        // 更新鉴别器和resultMap的关系
        namespaceDiscriminatorMap.put(e.getKey(), resultMap);
    }

    // 构建鉴别器
    return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
}

在生成了discriminator对应的ResultMapping对象之后,Mybatis会循环处理所有现有的鉴别器指定字段的值和resultMap的关联关系,这个处理操作主要是将现有的resultMap引用由局部改为全局。

完成这些操作之后,mybatis就会使用这些数据来构建一个Discriminator对象,负责创建Discriminator对象的构建器Discriminator.Builder在实现上基本就是简单的赋值操作:

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,处理了关于鉴别器的解析过程之后,我们回过头来继续看负责创建ResultMapping对象的buildResultMapping()方法:

/**
    * 构建ResultMapping实体
    *
    * @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           属性标记
    * @param resultSet       多结果集定义
    * @param foreignColumn   父数据列名称集合
    * @param lazy            懒加载标记
    */
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的时候,使用column= "{prop1=col1,prop2=col2}"
    // 这样的语法来配置多个列名传入到嵌套查询语句中的名称。其中prop1表示嵌套查询中的参数名称,col1表示主查询中列名称。
    List<ResultMapping> composites = parseCompositeColumnName(column);

    // 构建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方法的参数很多,但是不要慌,因为他的解析真的很简单!

buildResultMapping方法首先会借助resolveResultJavaType()resolveTypeHandler()方法解析出当前ResultMapping对象对应的java类型以及负责类型转换的类型转换处理器实例。

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

    if (javaType == null && property != null) {
        // 没有javaType根据类的元数据集合获取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()是对前面代码的一个补充,他可以通过反射操作来获取ResultMapping对应的javaType,如果无法通过反射获取javaType,那就默认赋值为Object.class.

resolveTypeHandler()方法负责实例化类型转换处理器,操作很简单,这里就不在赘述了:

 /**
    * 解析出指定的类型处理器
    *
    * @param javaType        java类型
    * @param typeHandlerType 类型处理器的类型
    * @return 类型处理器实例
    */
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) {
        // 创建一个新的类型处理器
        // not in registry, create a new one
        handler = typeHandlerRegistry.getInstance(javaType, typeHandlerType);
    }
    return handler;
}

完成这两个类型的处理之后,mybatis针对column属性值还会执行一次特殊的处理,在介绍associationcollection元素的配置时,提到column属性可以是普通的列名称定义,比如column="id",也可以是一个复合的属性描述,比如:column="{prop1=col1,prop2=col2}".

所以针对复合属性描述,mybatis会通过parseCompositeColumnName()方法将其解析成一组ResultMapping定义:

private List<ResultMapping> parseCompositeColumnName(String columnName) {
    List<ResultMapping> composites = new ArrayList<>();
    if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
        // 以 【{}=,】 作为分隔符处理内容
        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默认的未知类型的转换处理器*/
            ).build();

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

这一组ResultMapping定义有别于常规意义上的ResultMapping,它配置的是嵌套查询中,主查询结果对象中属性名称和子查询语句的参数关系.

最后buildResultMapping()方法就会通过前面处理好的属性完成一个ResultMapping对象的创建工作:

// 构建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();

在上面的方法调用中,针对引用的嵌套查询语句和嵌套映射,还提前做了一个局部ID转全局ID的操作.

buildResultMapping()parseCompositeColumnName()两个方法中,实际创建ResultMapping对象的工作都是由ResultMapping的构建器ResultMapping.Builder来完成的.

ResultMapping的创建工作

都讲到这里了,实在是避不开ResultMapping对象了,但是ResultMapping对象虽然看起来属性很多,可这些属性基本上咱们都做了一定的了解了,所以这个代码我就随手一贴,你就随手一看,咱也不大费周折的去看每一个属性了:

public class ResultMapping {

    /**
     * 配置
     */
    private Configuration configuration;
    /**
     * 属性名称
     */
    private String property;
    /**
     * 对应的列名称
     */
    private String column;
    /**
     * java类型
     */
    private Class<?> javaType;
    /**
     * jdbc类型
     */
    private JdbcType jdbcType;
    /**
     * 类型处理器
     */
    private TypeHandler<?> typeHandler;
    /**
     * 内部嵌套的或引用的ResultMap
     */
    private String nestedResultMapId;
    /**
     * 内部嵌套的或引用的查询语句
     */
    private String nestedQueryId;
    /**
     * 非空字段集合
     */
    private Set<String> notNullColumns;
    /**
     * 列名前缀
     */
    private String columnPrefix;
    /**
     * 返回类型标记
     * 构造参数,JDBC主键
     */
    private List<ResultFlag> flags;
    /**
     * resultMaps,嵌套的resultMap定义,是通过嵌套语句的column字段中以column={a=c,b=d}的方式定义出来的集合
     */
    private List<ResultMapping> composites;
    /**
     * resultSet
     */
    private String resultSet;

    /**
     * 外键
     */
    private String foreignColumn;

    /**
     * 懒加载标记
     */
    private boolean lazy;

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

ResultMapping.Builder的工作流程并不复杂,他提供的方法除了构造方法和build()方法之外基本都是简单的属性赋值操作.

构造方法的实现也不复杂,在重载形式为Builder(Configuration configuration, String property)的构造方法中,完成了部分属性的初始化操作:

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

这里面最特殊的一行应该就是lazy属性的初始化使用了全局懒加载配置.

build()方法的实现主要还是做了一些对属性二次处理和校验的工作:

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);

在没有指定类型转换处理器的前提下,根据javaType属性推断出可用的类型转换处理器实例:

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);
    }
}

以及对当前ResultMapping对象的完整性进行校验:

private void validate() {
    // 在一个ResultMapping定义中不能同时引用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);
    }
    // 没有类型处理程序就不应该有映射
    // 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 仅在嵌套的结果图中可选,但在其余部分中不可选
    // 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);
    }
    // 属性中应该有相同数量的列和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);
        }
    }
}

首先,一个ResultMapping对象是不能同时指定嵌套查询和嵌套结果映射的.

// 在一个ResultMapping定义中不能同时引用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);
}

其次,一个ResultMapping对象如果没有指定嵌套查询,也没有指定嵌套结果映射,那么,他就应该有一个可用的类型转换处理器,不然,是没办法完成将数据库数据转换为对象的操作的.

// 没有类型处理程序就不应该有映射
// 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);
}

同时,如果一个ResultMapping对象没有指定嵌套结果映射,那么就意味着这个ResultMapping对象必须指定了column属性,否则,他无法完成属性的映射或者执行子查询.

// column 仅在嵌套的结果图中可选,但在其余部分中不可选
// 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);
}

最后,因为在多结果集模式下,column属性将会配合着foreignColumn属性一起使用,且foreignColumn属性和column属性之间是顺序关联的.

所以,foreignColumn属性和column属性所配置列的数量应该是一致的.

// 属性中应该有相同数量的列和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);
    }
}

着手准备构建ResultMap对象

那么,到这里,我们就完成一个ResultMapping对象的创建工作,接下来,我们回过头来去看,在得到ResultMapping集合之后,mybatis是如何创建ResultMap对象的.

现在我们回到resultMapElement()方法中,此时我们已经完成了resultMap属性及其子元素的解析工作.

接下来,mybatis就会利用我们获取到这些数据,构建一个ResultMapResolver对象,来完成一个ResultMap对象的创建工作.

// 构建ResultMap解析器
ResultMapResolver resultMapResolver = new ResultMapResolver(
        builderAssistant
        , id /*resultMap的ID*/
        , typeClass /*返回类型*/
        , extend /*继承的ResultMap*/
        , discriminator /*鉴别器*/
        , resultMappings /*内部的ResultMapping集合*/
        , autoMapping /*自动映射*/
);

try {
    // 解析ResultMap
    return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
    // 解析ResultMap发生异常,将奖盖ResultMap放入未完成解析的ResultMap集合.
    configuration.addIncompleteResultMap(resultMapResolver);
    throw e;
}

如果在构建ResultMap对象的过程中触发了IncompleteElementException异常,整个ResultMapResolver对象都会被存入到Configuration对象的incompleteResultMaps集合中,等待下次重试.

这个重试实现和缓存引用的处理逻辑基本一致,因为可能会出现跨mapper文件引用resultMap配置的场景,所以提供了该重试机制.

回头看XMLMapperBuilderparse()方法,每解析一次mapper文件,都会尝试重新解析出现解析异常的ResultMap对象:

重试

ResultMapResolver对象和CacheRefResolver对象很像,它缓存了创建一个ResultMap对象所需的所有数据:

/**
 * ResultMap解析器
 *
 * @author Eduardo Macarron
 */
public class ResultMapResolver {
    /**
     * Mapper构造助手
     */
    private final MapperBuilderAssistant assistant;
    /**
     * resultMap的唯一标志
     */
    private final String id;
    /**
     * ResultMap的返回类型
     */
    private final Class<?> type;
    /**
     * 扩展(继承)的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对象的创建工作委托给MapperBuilderAssistant对象的addResultMap()方法来完成.

MapperBuilderAssistant对象的addResultMap()方法

addResultMap()方法的作用是创建一个ResultMap对象,并注册到Configuration对象的ResultMap注册表中,这个方法的实现代码很长,但是也不复杂.

在实现上,他做的工作主要就是解析ResultMap对象的继承关系,并合并具有继承关系的两个ResultMap对象的配置到子ResultMap对象中:

/**
    * 添加(注册)一个ResultMap集合
    *
    * @param id             ResultMap唯一标志
    * @param type           返回类型
    * @param extend         继承的ResultMap
    * @param discriminator  鉴别器
    * @param resultMappings 现有的ResultMapping集合
    * @param autoMapping    是否自动处理类型转换
    * @return ResultMap
    */
public ResultMap addResultMap(
        String id,
        Class<?> type,
        String extend,
        Discriminator discriminator,
        List<ResultMapping> resultMappings,
        Boolean autoMapping) {
    // 获取命名空间标志
    id = applyCurrentNamespace(id, false);
    // 继承的命名空间
    extend = applyCurrentNamespace(extend, true);

    if (extend != null) {
        if (!configuration.hasResultMap(extend)) {
            // 不存在引用(继承)的ResultMap,标记为incomplete,待第二次处理
            throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
        }
        // 获取被引入(继承)的ResultMaps
        ResultMap resultMap = configuration.getResultMap(extend);
        // 获取引入的resultMap的所有子节点配置
        List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
        // 本地覆盖继承
        extendedResultMappings.removeAll(resultMappings);
        // Remove parent constructor if this resultMap declares a constructor.
        // 当前resultMap是否声明了构造函数
        boolean declaresConstructor = false;
        for (ResultMapping resultMapping : resultMappings) {
            if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                // 当前resultMap声明了构造函数
                declaresConstructor = true;
                break;
            }
        }
        if (declaresConstructor) {
            // 如果已经声明了构造函数,准备移除父resultMap的构造函数
            Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
            while (extendedResultMappingsIter.hasNext()) {
                if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                    // 移除被继承的resultMap的构造函数
                    extendedResultMappingsIter.remove();
                }
            }
        }
        //合并自身的resultMap以及继承的resultMap的内容,获得最终的resultMap,这也意味着在启动时就创建了完整的resultMap,
        // 这样在运行时就不需要去检查继承的映射和构造器,有利于性能提升。
        resultMappings.addAll(extendedResultMappings);
    }

    // 构造ResultMap
    ResultMap resultMap = new ResultMap
            .Builder(
            configuration
            , id
            , type
            , resultMappings
            , autoMapping
    )
            .discriminator(discriminator)
            .build();

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

在实现上,首先addResultMap()方法会将当前待处理的ResultMap和被继承的ResultMapid通过applyCurrentNamespace()方法转换为全局引用标志,便于统一处理.

// 获取命名空间标志
id = applyCurrentNamespace(id, false);
// 继承的命名空间
extend = applyCurrentNamespace(extend, true);

然后,如果当前ResultMap对象存在继承的ResultMap对象,就将父ResultMap对象中的配置合并到当前ResultMap对象中.

在合并过程中,首先会校验被继承的父ResultMap对象是否已经配置到了Configuration中,如果没有的话,将会抛出一个IncompleteElementException,中断本次解析,等待重试.

// 获取被引入(继承)的ResultMaps
ResultMap resultMap = configuration.getResultMap(extend);
// 获取引入的resultMap的所有子节点配置
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
// 本地覆盖继承
extendedResultMappings.removeAll(resultMappings);

在拿到父ResultMap对象之后,addResultMap()方法会移除所有在当前ResultMap对象中定义的相同配置,

因为ResultMapping对象重写了equals()方法,因此具有相同属性名称(property)的ResultMapping对象会被认为是相同的:

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对象还配置了构造参数,那么所有构造参数对应的配置都会被移除:

// 当前resultMap是否声明了构造函数
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
    if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
        // 当前resultMap声明了构造函数
        declaresConstructor = true;
        break;
    }
}
if (declaresConstructor) {
    // 如果已经声明了构造函数,准备移除父resultMap的构造函数
    Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
    while (extendedResultMappingsIter.hasNext()) {
        if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            // 移除被继承的resultMap的构造函数
            extendedResultMappingsIter.remove();
        }
    }
}

最后将当前ResultMap对象的配置和父ResultMap对象的配置合二为一:

//合并自身的resultMap以及继承的resultMap的内容,获得最终的resultMap,这也意味着在启动时就创建了完整的resultMap,
// 这样在运行时就不需要去检查继承的映射和构造器,有利于性能提升。
resultMappings.addAll(extendedResultMappings);

这样就完成了ResultMap对象继承关系的处理,然后就是通过ResultMap的构建器来完成创建ResultMap对象的工作,并将得到的ResultMap对象注册到Configuration中.

ResultMap对象

无论是创建ResultMap对象还是注册ResultMap对象,这两个操作都涉及到了一些额外的操作,为了能够更好的理解ResultMap对象的创建和注册行为,我们先简单了解一下ResultMap对象.

public class ResultMap {
    /**
     * Mybatis配置对象
     */
    private Configuration configuration;
    /**
     * resultMap的唯一标志
     */
    private String id;
    /**
     * resultMap的返回类型
     */
    private Class<?> type;
    /**
     * resultMap下的所有节点
     */
    private List<ResultMapping> resultMappings;
    /**
     * resultMap下的所有id节点
     */
    private List<ResultMapping> idResultMappings;
    /**
     * resultMap下的所有构造器节点
     */
    private List<ResultMapping> constructorResultMappings;
    /**
     * resultMap下的所有普通属性节点
     */
    private List<ResultMapping> propertyResultMappings;
    /**
     * 映射处理的数据列名集合
     */
    private Set<String> mappedColumns;
    /**
     * 映射的所有javaBean属性名,包括ID,构造器,普通属性。
     */
    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的每个属性都给出了注释,如果有不了解的,在本文后面的内容中,还会有更详细的介绍.

创建ResultMap对象

简单了解了ResultMap对象的定义之后,我们回头继续看在MapperBuilderAssistantaddResultMap()方法创建ResultMap对象的操作:

// 构造ResultMap
    ResultMap resultMap = new ResultMap
            .Builder(
            configuration
            , id
            , type
            , resultMappings
            , autoMapping
    )
            .discriminator(discriminator)
            .build();

ResultMap.Builder是负责创建ResultMap对象的构建器,在上面的方法调用链中,除了build()方法之外,所有的方法实现均是简单的赋值操作.

在实现上与众不同的build()方法责任重大,他完成了ResultMap对象部分数据的初始化和校验工作.

build()方法的实现相对比较长,涉及到的属性也比较多,我们先总览一下代码,然后我们再细看该方法的实现:

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);
        // 获取当前列,包括复合列,
        // 复合列是在org.apache.ibatis.builder.MapperBuilderAssistant.parseCompositeColumnName(String)中解析的。
        // 所有的数据库列都被按顺序添加到resultMap.mappedColumns中
        final String column = resultMapping.getColumn();

        if (column != null) {
            // 添加映射的列名称
            resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
        } else if (resultMapping.isCompositeResult()) {
            // 当前是符合列
            for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
                // 获取复合列的列名称
                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标记,则添加到构造函数参数列表,否则添加到普通属性映射列表resultMap.propertyResultMappings
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            // 处理构造函数,注册到当前的构造函数映射集合中
            resultMap.constructorResultMappings.add(resultMapping);
            if (resultMapping.getProperty() != null) {
                // 添加到构造函数集合内
                constructorArgNames.add(resultMapping.getProperty());
            }
        } else {
            // 不是构造函数,直接添加到普通属性集合内
            resultMap.propertyResultMappings.add(resultMapping);
        }

        // 如果当前元素有ID标记,则添加到ID映射列表内
        if (resultMapping.getFlags().contains(ResultFlag.ID)) {
            // 如果是ID标志,添加到ID映射集合中
            resultMap.idResultMappings.add(resultMapping);
        }
    }

    // 循环结束

    // 如果当前resultMap没有声明ID属性,就把所有的属性都作为ID属性
    if (resultMap.idResultMappings.isEmpty()) {
        resultMap.idResultMappings.addAll(resultMap.resultMappings);
    }

    // 据声明的构造器参数名和类型,反射声明的类,
    // 检查其中是否包含对应参数名和类型的构造器,
    // 如果不存在匹配的构造器,就抛出运行时异常,这是为了确保运行时不会出现异常
    if (!constructorArgNames.isEmpty()) {
        // 获取构造参数中的名称集合
        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的构造器参数排序
        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的内部结构发生变更, 克隆一个不可修改的集合提供给用户
    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()方法对要创建的ResultMap对象的id属性做了最基础的校验,因为id属性是mybatis操作ResultMap对象时的唯一凭据.

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

之后build()方法会循环处理所有的ResultMappings配置,并根据ResultMappings的配置来完成部分核心属性的初始化工作.

比如,初始化当前ResultMap对象中负责描述是否存在嵌套查询语句和嵌套结果映射的hasNestedQuerieshasNestedResultMaps属性:

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

以及初始化负责维护当前ResultMap对象能够处理哪些数据列的集合mappedColumns:

final String column = resultMapping.getColumn();

if (column != null) {
    // 添加映射的列名称
    resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
} else if (resultMapping.isCompositeResult()) {
    // 当前是符合列
    for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
        // 获取复合列的列名称
        final String compositeColumn = compositeResultMapping.getColumn();
        if (compositeColumn != null) {
            // 添加映射的列名称
            resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
        }
    }
}

mappedColumns的取值有两种,一种是用户直接配置的简单列名称,一种用户配置的复合的属性描述中的数据列名称.

还有初始化负责维护当前ResultMap对象能够处理哪些属性的集合mappedProperties:

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

最后,根据每个ResultMapping对象的标记,是构造参数配置的放入到维护构造参数映射关系constructorResultMappings集合中,不是构造参数的放入到维护普通属性映射关系propertyResultMappings集合中.

// 如果本元素具有CONSTRUCTOR标记,则添加到构造函数参数列表,否则添加到普通属性映射列表resultMap.propertyResultMappings
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
    // 处理构造函数,注册到当前的构造函数映射集合中
    resultMap.constructorResultMappings.add(resultMapping);
    if (resultMapping.getProperty() != null) {
        // 添加到构造函数集合内
        constructorArgNames.add(resultMapping.getProperty());
    }
} else {
    // 不是构造函数,直接添加到普通属性集合内
    resultMap.propertyResultMappings.add(resultMapping);
}

而且针对构造参数配置,如果指定了构造参数的形参名称,还会将该形参名称放入到一个名为constructorArgNames的集合中,constructorArgNames是个局部变量,用于构造方法的校验工作.

如果ResultMapping对象还持有ResultFlag.ID标记,那么这个ResultMapping对象还会被放进负责维护id属性映射关系idResultMappings集合中.

// 如果当前元素有ID标记,则添加到ID映射列表内
if (resultMapping.getFlags().contains(ResultFlag.ID)) {
    // 如果是ID标志,添加到ID映射集合中
    resultMap.idResultMappings.add(resultMapping);
}

在循环处理完所有的ResultMappings配置之后,ResultMap对象属性的初始化工作基本就完成了,但是针对idResultMappings集合,还会有一步额外的操作.

不知道你是否还记得我们在介绍id元素的时候,因为滥用id元素造成的数据丢失问题?

相关文档: Mybatis源码之美:3.5.4.唯一标标识符--id元素

访问地址: https://www.jianshu.com/p/e51b125b8e6d

在那篇文章我们做了一个总结:

id元素标识的属性将会作为对象的标识符,该标识符会在比较对象实例的时候被使用.

但是没有说,如果没有配置id元素,如何比较对象实例.

针对没有配置id元素的场景,build()方法会把当前ResultMap对象的所有ResultMapping配置放入到idResultMappings集合中,用来作为唯一标识:

// 如果当前resultMap没有声明ID属性,就把所有的属性都作为ID属性
if (resultMap.idResultMappings.isEmpty()) {
    resultMap.idResultMappings.addAll(resultMap.resultMappings);
}

到这里,ResultMap对象属性的初始化工作才算完成,接下来就是构造方法的校验工作了,如果用户配置构造参数的时候指定了构造参数的形参名称,那么build()方法就会根据形参名称去寻找相应的构造方法,并进行基础的校验工作:

// 据声明的构造器参数名和类型,反射声明的类,
// 检查其中是否包含对应参数名和类型的构造器,
// 如果不存在匹配的构造器,就抛出运行时异常,这是为了确保运行时不会出现异常
if (!constructorArgNames.isEmpty()) {
    // 获取构造参数中的名称集合
    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的构造器参数排序
    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.配置构造方法的constructor元素一文中,我们讲从版本3.4.3开始,mybatis开始支持根据参数名称匹配所对应的构造方法,这里就是对这一特性的处理和校验.

argNamesOfMatchingConstructor()方法负责根据现有的constructorArgNames形参名称集合,来寻找相匹配的第一个构造方法,并返回匹配构造方法的有序形参名称集合.

因为该方法的解析涉及到一些额外的操作,所以我们待会再补充该方法的实现细节,现在让我们先完成build()方法的后续操作.

build()方法得到有序形参名称集合之后,会利用该集合对现有的constructorResultMappings集合进行排序,这样constructorResultMappings集合中存放的配置就和实际的构造方法顺序对应上了.

最后,build()方法借助于CollectionsunmodifiableList()方法将上面配置的这些集合转换为不可变更的集合,至此就完成了一个ResultMap对象的创建工作了.

// lock down collections
// 为了避免resultMap的内部结构发生变更, 克隆一个不可修改的集合提供给用户
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()方法

现在我们可以回头看一下argNamesOfMatchingConstructor()方法的实现了.

这个方法的实现逻辑是这样的,先获取返回对象类型的所有构造方法,然后筛选出构造参数数量和现有constructorArgNames中维护的形参名称数量一致的构造方法.

private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
    // 获取resultMap对应的javabean的构造函数集合
    Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();

    for (Constructor<?> constructor : constructors) {
        // 获取当前构造函数的入参列表
        Class<?>[] paramTypes = constructor.getParameterTypes();
        // 处理参数列表和当前入参数量一致的构造函数
        if (constructorArgNames.size() == paramTypes.length) {
            // 获取构造参数的入参名称集合(有序)
            List<String> paramNames = getArgNames(constructor);

            if (constructorArgNames.containsAll(paramNames)  /*参数名称一致*/
                    && argTypesMatch(
                    constructorArgNames
                    , paramTypes /*真正的参数类型集合*/
                    , paramNames/*真正的参数名称集合*/
            )/*类型是否一致*/
            ) {
                return paramNames;
            }
        }
    }
    return null;
}

然后用匹配到的构造方法的参数类型和形参名称,去一一对应用户配置的构造参数名称和类型,如果能够匹配,则表示该构造方法是一个有效的构造方法,返回该构造方法的形参名称集合即可.

// 获取构造参数的入参名称集合(有序)
List<String> paramNames = getArgNames(constructor);

if (constructorArgNames.containsAll(paramNames)  /*参数名称一致*/
        && argTypesMatch(
        constructorArgNames
        , paramTypes /*真正的参数类型集合*/
        , paramNames/*真正的参数名称集合*/
)/*类型是否一致*/
) {
    return paramNames;
}

负责获取构造方法形参名称的getArgNames()方法的实现别有洞天:

private List<String> getArgNames(Constructor<?> constructor) {
    List<String> paramNames = new ArrayList<>();
    List<String> actualParamNames = null;
    // 获取参数列表中的注解集合,每一个构造参数都对应一个Annotation数组。
    final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();

    /*
        * 构造参数的数量
        */
    int paramCount = paramAnnotations.length;

    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        // 处理每一个构造参数
        String name = null;
        for (Annotation annotation : paramAnnotations[paramIndex]) {
            // 寻找当前构造参数上的Param注解
            if (annotation instanceof Param) {
                name = ((Param) annotation).value();
                break;
            }
        }
        if (name == null && resultMap.configuration.isUseActualParamName()) {
            // 如果没有添加Param注解,同时还开启了使用真实参数的功能的话,则使用真实参数名称
            if (actualParamNames == null) {
                //获取构造参数的所有入参的参数名称集合
                actualParamNames = ParamNameUtil.getParamNames(constructor);
            }
            if (actualParamNames.size() > paramIndex) {

                name = actualParamNames.get(paramIndex);
            }
        }
        // 添加参数名称,如果没有找到名称的话,则使用arg+参数索引
        paramNames.add(name != null ? name : "arg" + paramIndex);
    }
    return paramNames;
}

在所有配置形参名称的方案中,通过@Param注解配置的属性名优先级最高,开启了useActualParamName特性下的真实形参名称略低,保底的形参名称则是argN,其中N表示形参索引.

负责获取真实形参名称ParamNameUtilgetParamNames()方法实现比较简单,经过一次跳转,该方法最终是借助于反射机制来完成的形参名称获取操作.

/**
    * 获取指定方法的所有入参的参数名称集合
    *
    * @param executable 方法(Executable表示普通方法和构造方法的通用父类)
    * @return 指定方法的所有入参的参数名称集合
    */
private static List<String> getParameterNames(Executable executable) {
    final List<String> names = new ArrayList<>();
    // 获取方法所有入参
    final Parameter[] params = executable.getParameters();
    for (Parameter param : params) {
        // 添加参数名称
        names.add(param.getName());
    }
    // 返回
    return names;
}

argNamesOfMatchingConstructor方法中,除了getArgNames()方法之外,还有一个argTypesMatch()方法,该方法用来校验指定构造方法的形参名称和类型,能否和用户配置的形参和类型对应上:

/**
    * 匹配构造参数的类型
    *
    * @param constructorArgNames 构造函数的参数名称集合
    * @param paramTypes          参数类型
    * @param paramNames          参数名称
    * @return 是否匹配
    */
private boolean argTypesMatch(final List<String> constructorArgNames,
                                Class<?>[] paramTypes, List<String> paramNames) {
    for (int i = 0; i < constructorArgNames.size(); i++) {
        // 处理每一个构造参数

        // 获取参数类型
        Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
        // 获取构造函数的参数类型
        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;
}

至此,构建ResultMap对象涉及到的方法就已经了解完毕了,现在我们回过头去看,向Configuration对象注册ResultMap对象时又执行了那些额外操作?

注册ResultMap对象

Configuration对象的addResultMap()方法用于注册ResultMap对象,该方法的实现除了会将ResultMap对象存入到resultMaps集合中,还会执行以下额外的操作:

public void addResultMap(ResultMap rm) {
    resultMaps.put(rm.getId(), rm);
    /*检查当前resultMap内的鉴别器是否嵌套ResultMap*/
    checkLocallyForDiscriminatedNestedResultMaps(rm);
    /*检查所有的ResultMap内的鉴别器是否嵌套ResultMap*/
    checkGloballyForDiscriminatedNestedResultMaps(rm);
}

其中checkLocallyForDiscriminatedNestedResultMaps()方法用于检测当前ResultMap对象中是否配置了Discriminator,以及Discriminator中是否包含嵌套结果映射.

// Slow but a one time cost. A better solution is welcome.
protected void checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm) {
    if (!rm.hasNestedResultMaps() && rm.getDiscriminator() != null) {
        // 当前ResultMap不含有嵌套的ResultMap,同时含有鉴别器
        for (Map.Entry<String, String> entry : rm.getDiscriminator().getDiscriminatorMap().entrySet()) {
            // 获取鉴别器对应的ResultMap
            String discriminatedResultMapName = entry.getValue();

            // 已经加载了鉴别器对应的ResultMap
            if (hasResultMap(discriminatedResultMapName)) {
                // 获取鉴别器对应的ResultMap
                ResultMap discriminatedResultMap = resultMaps.get(discriminatedResultMapName);

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

如果当前ResultMap对象中配置了Discriminator,且Discriminator中包含嵌套结果映射,那么就意味着当前ResultMap对象也包含了嵌套结果映射.

毕竟ResultMap包含了Discriminator,Discriminator包含了嵌套结果映射,所以ResultMap包含嵌套结果映射,这个逻辑没什么问题.

但是,有一点请注意,在判断Discriminator中是否包含嵌套结果映射时,有一个前置条件是被引用的嵌套结果映射必须已经存在于当前Configuration对象中:

// ... 省略 ...
if (hasResultMap(discriminatedResultMapName)) {
// ... 处理嵌套结果映射 ...
}
// ... 省略 ...

这是因为,前面我们构建Discriminator对象时,在解析case元素的resultMap属性后,没有进行任何校验:

// 解析case代码块的ResultMap标记
String resultMap = caseChild.getStringAttribute("resultMap" /*使用指定的resultMap*/
        , processNestedResultMappings(caseChild, resultMappings, resultType/*如果没有指定resultMap,则动态生成ResultMap实例*/
        )
);

因此,通过case元素的resultMap属性引用的ResultMap对象此时可能还没有初始化.

如果被引用的ResultMap对象还没有初始化,checkLocallyForDiscriminatedNestedResultMaps()方法就无法得知被引用的ResultMap对象中是否包含嵌套结果映射,因此也就没有办法更新当前ResultMap对象中的hasNestedResultMaps标记.

checkGloballyForDiscriminatedNestedResultMaps()方法是对checkLocallyForDiscriminatedNestedResultMaps()方法在这种特殊场景下的一个补充.

protected void checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm) {
    if (rm.hasNestedResultMaps()) {
        // 当前的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) {
                    // 已经加载了鉴别器对应的ResultMap
                    Collection<String> discriminatedResultMapNames = entryResultMap.getDiscriminator().getDiscriminatorMap().values();
                    // 获取鉴别器引用了当前的resultMap
                    if (discriminatedResultMapNames.contains(rm.getId())) {
                        // 更新entryResultMap的hasNestedResultMaps字段为true.
                        entryResultMap.forceNestedResultMaps();
                    }
                }
            }
        }
    }
}

当注册了一个包含嵌套结果映射的ResultMap对象时,checkGloballyForDiscriminatedNestedResultMaps()方法就会获取所有通过鉴别器引用了当前ResultMap对象的ResultMap对象,并更新其hasNestedResultMaps标记.

到这里,我们就完整的了解了创建和注册ResultMap对象的流程.

写在最后

这篇文章相对比较复杂,涉及到的内容也比较多,因为逻辑比较复杂,涉及到的代码层级也比较深,因此建议配合着源码多看几遍.

加油!

就酱,告辞!

这条 gai 最靓的仔
上一篇下一篇

猜你喜欢

热点阅读