MyBatis3源码深度解析(二十五)级联映射与关联查询(二)级联映射的实现原理

前言

上一节【MyBatis3源码深度解析(二十四)级联映射与关联查询(一)级联映射的使用】通过编写一个测试案例,学习了如何使用<resultMap>标签实现MyBatis的一对多、一对一级联映射。

本节来研究一下MyBatis级联映射的实现原理。先说明一下本节使用的测试案例代码:

<!--UserMapper.xml-->
<resultMap id="fullUser" type="User">
    <id column="user_id" property="userId"/>
    <result column="name" property="name"/>
    <!--<result column="age" property="age"/>-->
    <!--<result column="phone" property="phone"/>-->
    <result column="birthday" property="birthday"/>
    <collection property="orderList"
                select="com.star.mybatis.mapper.OrderMapper.listOrderByUserId"
                ofType="Order"
                javaType="List"
                column="user_id">
    </collection>
</resultMap>

age和phone属性被注释掉了,是故意的,下面分析会用到。

@Test
public void testOne2ManyQuery() {
    User user = userMapper.getFullUserById(1);
    System.out.println(user.toString());
}

10.2 MyBatis级联映射的实现原理

10.2.1 ResultMap详解

MyBatis是一个半自动化的ORM框架,可以将数据库中的记录转换为Java实体对象,但是Java实体属性通常采用驼峰命名法,而数据库表字段习惯采用下划线分割命名法,因此需要用户指定Java实体属性与数据库表字段之间的映射关系。

Mapper配置中的<resultMap>标签,就用于建立Java实体属性与数据库表字段之间的映射关系,例如本节的测试案例代码。

测试案例的配置中,每个ResultMap有一个全局唯一的ID,即<resultMap>标签的id属性;还会通过type属性指定与哪一个Java实体进行映射。

在<resultMap>标签中,需要使用<id>或<result>标签配置具体的某个表字段与Java实体属性之间的映射关系。数据库主键通常使用<id>标签建立映射关系,普通数据库字段则使用<result>标签。

除了属性映射,ResultMap还支持构造器映射,即<constructor>标签,例如:

<!--UserMapper.xml-->
<resultMap id="UserMap" type="User">
    <constructor>
        <idArg column="user_id" javaType="Integer"/>
        <arg column="name" javaType="String"/>
    </constructor>
    <result column="age" property="age"/>
    <result column="phone" property="phone"/>
    <result column="birthday" property="birthday"/>
</resultMap>

使用构造器映射的前提是,Java实体中存在与之相对应的构造方法。<idArg>标签用于配置数据库主键的映射,<arg>标签用于配置数据库普通字段的映射。

最后,总结一下<resultMap>标签的各个子标签的作用:

  • <id>:用于配置数据库主键的映射,标记出主键,提高整体性能。
  • <result>:用于配置数据库普通字段的映射。
  • <collection>:用于配置一对多关联映射,可以关联一个外部的查询Mapper或者配置一个嵌套的ResultMap。
  • <association>:用于配置一对一关联映射,可以关联一个外部的查询Mapper或者配置一个嵌套的ResultMap。
  • <discriminator>:用于配置根据字段值使用不同的ResultMap。该标签有一个子标签<case>,用于枚举字段值对应的ResultMap,类似于Java中的switch语法。
  • <constructor>:用于建立构造器映射。它有两个子标签,<idArg>标签用于配置数据库主键的映射,标记出主键,提高整体性能;<arg>标签用于配置数据库普通字段的映射。

10.2.2 ResultMap解析过程

MyBatis在启动时,所有配置信息都会被转换为Java对象,通过<resultMap>标签配置的结果集映射信息将会被转换为ResultMap对象。 其定义如下:

源码1org.apache.ibatis.mapping.ResultMap

public class ResultMap {
    private Configuration configuration;
    // <resultMap>标签的id属性
    private String id;
    // <resultMap>标签的type属性,指定与数据库表建立映射关系的Java实体
    private Class<?> type;
    // <result>标签配置的映射信息
    private List<ResultMapping> resultMappings;
    // <id>标签配置的主键映射信息
    private List<ResultMapping> idResultMappings;
    // <constructor>标签配置的构造器映射信息
    private List<ResultMapping> constructorResultMappings;
    // <result>标签配置的结果集映射信息
    private List<ResultMapping> propertyResultMappings;
    // 存放所有映射的数据库字段信息
    private Set<String> mappedColumns;
    // 存放所有映射的属性信息
    private Set<String> mappedProperties;
    // <discriminator>标签配置的鉴别器信息
    private Discriminator discriminator;
    // 是否有嵌套的<resultMap>
    private boolean hasNestedResultMaps;
    // 是否存在嵌套查询
    private boolean hasNestedQueries;
    // <resultMap>标签的autoMapping属性,是否自动映射
    private Boolean autoMapping;
    // ......
}

ResultMap类中定义的属性的含义如 源码1 中的注释所示。其中有几个属性需要单独解释下:

  • mappedColumns:用于存放所有映射的数据库字段信息。当使用columnPrefix属性配置了前缀时,MyBatis会对mappedColumns属性进行遍历,为所有数据库字段追加columnPrefix属性配置的前缀。
  • hasNestedResultMaps:该属性用于标识是否有嵌套的ResultMap。当使用<association>或<collection>标签,并以JOIN子句的方式配置一对一或一对多级联映射时,<association>或<collection>标签就相当于一个嵌套的ResultMap,此时hasNestedResultMaps属性为true。
  • hasNestedQueries:该属性用于标识是否有嵌套查询。当使用<association>或<collection>标签,并以外部Mapper的方式配置一对一或一对多级联映射时,<association>或<collection>标签存在嵌套查询,此时hasNestedResultMaps属性为true。
  • autoMapping:该标签用于标识是否开启自动映射。为true时,即使未使用<id>或<result>标签配置映射字段,MyBatis也会自动对这些字段进行映射。

MyBatis的Mapper配置信息的解析都是通过XMLMapperBuilder类完成的,该类提供了一个parse()方法,用于解析Mapper中的所有配置信息。

源码2org.apache.ibatis.builder.xml.XMLMapperBuilder

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        // 调用XPathParser的evalNode()方法获取根节点对应的XNode对象
        // 在调用configurationElement()方法解析该XNode对象
        configurationElement(parser.evalNode("/mapper"));
        
    // ......
}

由 源码2 可知,在XMLMapperBuilder类的parse()方法中,会调用configurationElement()方法解析<mapper>标签对应的XNode对象。

源码3org.apache.ibatis.builder.xml.XMLMapperBuilder

private void configurationElement(XNode context) {
    try {
        // 获取和配置命名空间
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.isEmpty()) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        // 解析<cache-ref>标签
        cacheRefElement(context.evalNode("cache-ref"));
        // 解析<cache>标签
        cacheElement(context.evalNode("cache"));
        // 解析<parameterMap>标签
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // 解析<resultMap>标签
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // 解析<sql>标签
        sqlElement(context.evalNodes("/mapper/sql"));
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } // catch ...
}

由 源码3 可知,在XMLMapperBuilder类的configurationElement()方法中,会逐个解析各种标签,其中就有调用resultMapElements()方法解析<resultMap>标签。

源码4org.apache.ibatis.builder.xml.XMLMapperBuilder

private void resultMapElements(List<XNode> list) {
    // 对全部<resultMap>标签进行遍历
    for (XNode resultMapNode : list) {
        try {
            resultMapElement(resultMapNode);
        } catch (IncompleteElementException e) {
            // ignore, it will be retried
        }
    }
}

private ResultMap resultMapElement(XNode resultMapNode) {
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
}

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings,
                                   Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获取<resultMap>标签的属性,按照type→ofType→resultType→javaType的顺序获取
    // 如果type属性为空,则获取ofType属性,以此类推
    String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
        typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    // 获取<resultMap>标签的子标签
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
        if ("constructor".equals(resultChild.getName())) {
            // 处理<constructor>标签
            processConstructorElement(resultChild, typeClass, resultMappings);
        } else if ("discriminator".equals(resultChild.getName())) {
            // 处理<discriminator>标签
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
        } else {
            // 处理其他标签
            List<ResultFlag> flags = new ArrayList<>();
            if ("id".equals(resultChild.getName())) {
                flags.add(ResultFlag.ID);
            }
            // 将各标签转换为ResultMapping对象并添加到resultMappings集合中
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
    }
    // 获取id、extend、autoMapping属性
    String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    // 构造ResultMapResolver对象
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator,
            resultMappings, autoMapping);
    try {
        // 调用ResultMapResolver对象的resolve()方法
        return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
    }
}

由 源码4 可知,在XMLMapperBuilder类的resultMapElements()方法中,会使用for循环语句对全部<resultMap>标签进行遍历,每一个<resultMap>标签均调用resultMapElement()方法。

resultMapElement()方法中,会获取<resultMap>标签的所有属性信息,对<id>、<constructor>、<discriminator>等标签进行解析,接着创建一个ResultMapResolver对象,调用ResultMapResolver对象的resolve()方法返回一个ResultMap对象。

源码5org.apache.ibatis.builder.ResultMapResolver

public class ResultMapResolver {
    private final MapperBuilderAssistant assistant;
    private final String id;
    private final Class<?> type;
    private final String extend;
    private final Discriminator discriminator;
    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;
    }

    public ResultMap resolve() {
        return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings,
                this.autoMapping);
    }

}

由 源码5 可知,在ResultMapResolver的resolve()方法中,会调用MapperBuilderAssistant对象的addResultMap()方法创建ResultMap对象。

源码6org.apache.ibatis.builder.MapperBuilderAssistant

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) {
        // 继承了其他ResultMap的情况 ......
    }
    // 通过建造者模式创建ResultMap对象
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
            .discriminator(discriminator).build();
    // 将ResultMap对象设置到Configuration对象中
    configuration.addResultMap(resultMap);
    return resultMap;
}

由 源码6 可知,在MapperBuilderAssistant对象的addResultMap()方法中,会通过建造者模式创建ResultMap对象,并将ResultMap对象设置到Configuration对象中。

借助Debug工具,可以查看测试案例的<resultMap>标签的解析结果:

由图可知,数据库字段与Java实体属性的映射关系封装在一个ResultMapping对象中。重点关注一下orderList属性,其对应的ResultMapping对象信息如下,其中<collection>标签的select属性被封装在ResultMapping对象nestedQueryId属性中:

10.2.3 级联映射的实现原理

默认情况下,MyBatis会选择PreparedStatement操作数据库,因此在执行测试案例中的getFullUserById()查询操作时,会调用PreparedStatementHandler对象的query()方法。

源码7org.apache.ibatis.executor.statement.PreparedStatementHandler

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 执行查询操作
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    // 处理结果集
    return resultSetHandler.handleResultSets(ps);
}

通过Debug工具,可以发现query()方法被调用了两次,第一次的查询语句是select * from user where user_id = 1,第二次的查询语句是select * from `order` where user_id = 1

这和上一节的分析结果是一致的,在接下来的分析中也证实了这种结果。

由 源码7 可知,在PreparedStatementHandler对象的query()方法中,会在完成查询操作后,调用ResultSetHandler对象的handleResultSets()方法处理结果集。

ResultSetHandler接口只有一个实现类,即DefaultResultSetHandler类。

源码8org.apache.ibatis.executor.resultset.DefaultResultSetHandler

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    final List<Object> multipleResults = new ArrayList<>();
    int resultSetCount = 0;
    // 获取第一个结果集,并将其包装成ResultSetWrapper
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 获取本次查询对应的ResultMap对象
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
        ResultMap resultMap = resultMaps.get(resultSetCount);
        // 真正处理结果集
        handleResultSet(rsw, resultMap, multipleResults, null);
        // 获取下一个结果集
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
    }

    // ......
}

由 源码8 可知,在DefaultResultSetHandler对象的handleResultSets()方法中,结果集会被包装成ResultSetWrapper对象,真正处理结果集的方法是handleResultSet(),其参数包括ResultSetWrapper对象,以及从MappedStatement中取出来的ResultMap对象。

源码9org.apache.ibatis.executor.resultset.DefaultResultSetHandler

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
                             ResultMapping parentMapping) throws SQLException {
    try {
        if (parentMapping != null) {
            // 当配置了<select>标签的resultSets属性时,parentMapping的值不为null
            handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
        } else if (resultHandler == null) {
            // 当没有指定ResultHandler时,创建默认的DefaultResultHandler
            DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
            handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
            multipleResults.add(defaultResultHandler.getResultList());
        } else {
            // 最终都会调用handleRowValues()方法
            handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
    } finally {
        closeResultSet(rsw.getResultSet());
    }
}

由 源码9 可知,在DefaultResultSetHandler对象的handleResultSet()方法中,针对不同的情况做了一些逻辑判断,但最终都会调用handleRowValues()方法进行处理。

源码10org.apache.ibatis.executor.resultset.DefaultResultSetHandler

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
        RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    // 判断是否有嵌套ResultMap
    if (resultMap.hasNestedResultMaps()) {
        ensureNoRowBounds();
        checkResultHandler();
        handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
        handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}

由 源码10 可知,在DefaultResultSetHandler对象的handleRowValues()方法中,会判断是否有嵌套ResultMap。如果有,则调用handleRowValuesForNestedResultMap()方法进行处理,否则调用handleRowValuesForSimpleResultMap()方法进行处理。

10.2.3.1 handleRowValuesForSimpleResultMap()

在【10.2.2 ResultMap解析过程】中指出,当使用<association>或<collection>标签,并以JOIN子句的方式配置一对一或一对多级联映射时,<association>或<collection>标签就相当于一个嵌套的ResultMap,此时hasNestedResultMaps属性为true。当使用外部Mapper的方式时,hasNestedResultMaps属性为false。

下面分析一下hasNestedResultMaps属性为false时的情况:

源码11org.apache.ibatis.executor.resultset.DefaultResultSetHandler

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
        ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    skipRows(resultSet, rowBounds);
    // 遍历结果集对象,处理每一行数据
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
        ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
        // 将结果集中的一行数据转换为Java实体对象
        Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
}

由 源码11 可知,在DefaultResultSetHandler对象的handleRowValuesForSimpleResultMap()方法中,会遍历结果集对象,处理每一行数据,调用getRowValue()方法将结果集中的每一行数据转换为Java实体对象。

源码12org.apache.ibatis.executor.resultset.DefaultResultSetHandler

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // (1)创建结果对象
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        if (shouldApplyAutomaticMappings(resultMap, false)) {
            // (2)处理自动映射,对未通过<result>等标签配置的映射进行处理
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        // (3)处理<result>等标签配置的映射信息
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
}

由 源码12 可知,在DefaultResultSetHandler对象的getRowValue()方法做了三件事情:

(1)getRowValue()方法的第一步,调用createResultObject()方法创建结果对象。

源码13org.apache.ibatis.executor.resultset.DefaultResultSetHandler

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
  List<Object> constructorArgs, String columnPrefix) throws SQLException {
    // ......
    if (!constructorMappings.isEmpty()) {
        // 根据<constructor>标签配置的构造器映射找到构造方法
        return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,
            columnPrefix);
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
        // 使用默认构造方法
        return objectFactory.create(resultType);
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
        // 使用标注了@AutomapConstructor注解的构造方法
        return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
            constructorArgs);
    }
    // throw ...
}

由 源码13 可知,createResultObject()方法会在按照【<constructor>标签配置的构造器映射→默认构造方法→标注了@AutomapConstructor注解的构造方法】的顺序找到结果对象的构造方法,通过构造方法创建一个结果对象。

(2)getRowValue()方法的第二步,调用applyAutomaticMappings()方法处理自动映射,对未通过<result>等标签配置的数据库字段与Java实体属性的映射进行处理。

源码14org.apache.ibatis.executor.resultset.DefaultResultSetHandler

private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
                                       String columnPrefix) throws SQLException {
    // 获取需要进行自动映射的UnMappedColumnAutoMapping对象
    // UnMappedColumnAutoMapping封装了数据库字段与Java实体属性的关联关系
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
        // 遍历
        for (UnMappedColumnAutoMapping mapping : autoMapping) {
            // 获取数据库记录中该字段的值
            final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
            if (value != null) {
                foundValues = true;
            }
            if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
                // 调用实体对象对应的MetaObject对象的setValue()方法进行赋值
                metaObject.setValue(mapping.property, value);
            }
        }
    }
    return foundValues;
}

由 源码14 可知,applyAutomaticMappings()方法首先会找到需要进行自动映射的UnMappedColumnAutoMapping对象集合,UnMappedColumnAutoMapping对象封装了没有通过<result>等标签配置的数据库字段与Java实体属性的对应关系。

接着对UnMappedColumnAutoMapping对象集合进行遍历,获取数据库字段的值,并调用Java实体对象对应的MetaObject对象的setValue()方法进行赋值。

借助Dubug工具,可以查看测试案例执行到这一步的结果:

由图可知,createAutomaticMappings()找到了2个需要进行自动映射的属性,恰好就是测试案例中注释掉的age属性和phone属性。整个方法执行完后,可以发现User实体的age属性和phone属性已被成功赋值。

(3)getRowValue()方法的第三步,调用applyPropertyMappings()方法处理通过<result>等标签配置的数据库字段与Java实体属性的映射。

源码15org.apache.ibatis.executor.resultset.DefaultResultSetHandler

private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
                                      ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    final Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    // 获取通过标签配置的映射集合
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    // 遍历映射集合
    for (ResultMapping propertyMapping : propertyMappings) {
        // 获取数据库字段名(包含前缀处理)
        String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
        if (propertyMapping.getNestedResultMapId() != null) {
            column = null;
        }
        if (propertyMapping.isCompositeResult()
                || column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))
                || propertyMapping.getResultSet() != null) {
            // 获取数据库字段对应的值
            Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader,
                    columnPrefix);
            // 获取Java实体属性的名称
            final String property = propertyMapping.getProperty();
            if (property == null) {
                continue;
            }
            if (value == DEFERRED) {
                foundValues = true;
                continue;
            }
            if (value != null) {
                foundValues = true;
            }
            if (value != null
                    || configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()) {
                // 为Java实体对象的属性赋值
                metaObject.setValue(property, value);
            }
        }
    }
    return foundValues;
}

由 源码15 可知,applyPropertyMappings()方法的逻辑很清晰,即获取所有通过标签指定的映射信息并遍历,然后找到数据库字段对应的值,最后为Java实体赋值。

借助Dubug工具,可以查看测试案例执行时获取的映射集合:

由图可知,映射集合共有4个ResultMapping对象,分别对应Mapper配置文件中配置的userId、name、birthday、orderList属性。要注意的是,orderList属性是一个<collection>标签,它的select属性被记录在ResultMapping对象的nestedQueryId属性上。

测试案例执行时applyPropertyMappings()方法的执行结果:

由图可知,applyPropertyMappings()方法执行完后,用户信息及其关联的订单信息均被查询出来了。

实际上,也是在applyPropertyMappings()方法中,<collection>标签中配置的外部Mapper被执行了。

(4)深入applyPropertyMappings()方法中的getPropertyMappingValue()方法,执行外部Mapper。

源码16org.apache.ibatis.executor.resultset.DefaultResultSetHandler

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
                                       ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    // 如果ResultMapping对象的nestedQueryId属性不为空,则进行嵌套查询
    if (propertyMapping.getNestedQueryId() != null) {
        return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    }
    // 正常获取数据库字段值的逻辑
    if (propertyMapping.getResultSet() != null) {
        addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
        return DEFERRED;
    } else {
        final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
        final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
        return typeHandler.getResult(rs, column);
    }
}

由 源码16 可知,getPropertyMappingValue()方法会判断ResultMapping对象的nestedQueryId属性是否不为空,如果不为空则进行嵌套查询,否则执行正常获取数据库字段值的逻辑。

在测试案例中,orderList属性是一个<collection>标签,它的select属性被记录在ResultMapping对象的nestedQueryId属性上,因此在获取该属性对应的值时,会进行嵌套查询。

源码17org.apache.ibatis.executor.resultset.DefaultResultSetHandler

private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
                                          ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    // 获取外部Mapper的ID
    final String nestedQueryId = propertyMapping.getNestedQueryId();
    final String property = propertyMapping.getProperty();
    // 根据Mapper的ID获取对应的MappedStatement对象,并准备参数
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
            nestedQueryParameterType, columnPrefix);
    Object value = null;
    if (nestedQueryParameterObject != null) {
        
        // ......
        
        } else {
            final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
                    nestedQueryParameterObject, targetType, key, nestedBoundSql);
            if (propertyMapping.isLazy()) {
                lazyLoader.addLoader(property, metaResultObject, resultLoader);
                value = DEFERRED;
            } else {
                // 获取结果
                value = resultLoader.loadResult();
            }
        }
    }
    return value;
}

由 源码17 可知,getNestedQueryMappingValue()方法会根据外部Mapper的ID从Configuration对象中获取对应的MappedStatement对象;然后利用MappedStatement对象创建一个ResultLoader对象,在调用ResultLoader对象的loadResult()方法获取结果。

源码18org.apache.ibatis.executor.loader.ResultLoader

public Object loadResult() throws SQLException {
    List<Object> list = selectList();
    resultObject = resultExtractor.extractObjectFromList(list, targetType);
    return resultObject;
}

private <E> List<E> selectList() throws SQLException {
    Executor localExecutor = executor;
    if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
        localExecutor = newExecutor();
    }
    try {
        // 调用Executor对象的query()方法执行查询操作
        return localExecutor.query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER,
                cacheKey, boundSql);
    } // finally ......
}

由 源码18 可知,ResultLoader对象的loadResult()方法会转调selectList()方法,而该方法会调用Executor对象的query()方法执行查询操作。

在测试案例中,通过这一次额外查询,orderList属性的值也被填充好了:

至此,getRowValue()方法(源码12)全部执行完毕,handleRowValuesForSimpleResultMap()方法(源码11)得到了最终的查询结果:

10.2.3.2 handleRowValuesForNestedResultMap()

下面再来看看 源码10 中,hasNestedResultMaps属性为true时的情况,即存在嵌套ResultMap,此时会调用handleRowValuesForNestedResultMap()方法。

这个方法与上面分析的逻辑不同的地方在于:applyPropertyMappings()方法中不会对orderList属性进行赋值,而是在这后面加了一个applyNestedResultMappings()方法来赋值。

具体源码不再展开,分析方法和上面的分析逻辑类似。

······

本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灰色孤星A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值