文章目录
- 前言
- 10.2 MyBatis级联映射的实现原理
- 10.2.1 ResultMap详解
- 10.2.2 ResultMap解析过程
- 10.2.3 级联映射的实现原理
- 10.2.3.1 handleRowValuesForSimpleResultMap()
- 10.2.3.2 handleRowValuesForNestedResultMap()
前言
上一节【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对象。 其定义如下:
源码1:org.apache.ibatis.mapping.ResultMappublic 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中的所有配置信息。
源码2:org.apache.ibatis.builder.xml.XMLMapperBuilderpublic void parse() {if (!configuration.isResourceLoaded(resource)) {// 调用XPathParser的evalNode()方法获取根节点对应的XNode对象// 在调用configurationElement()方法解析该XNode对象configurationElement(parser.evalNode("/mapper"));// ......
}
由 源码2 可知,在XMLMapperBuilder类的parse()
方法中,会调用configurationElement()
方法解析<mapper>标签对应的XNode对象。
源码3:org.apache.ibatis.builder.xml.XMLMapperBuilderprivate 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>标签。
源码4:org.apache.ibatis.builder.xml.XMLMapperBuilderprivate 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对象。
源码5:org.apache.ibatis.builder.ResultMapResolverpublic 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对象。
源码6:org.apache.ibatis.builder.MapperBuilderAssistantpublic 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()
方法。
源码7:org.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类。
源码8:org.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;// 获取第一个结果集,并将其包装成ResultSetWrapperResultSetWrapper 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对象。
源码9:org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,ResultMapping parentMapping) throws SQLException {try {if (parentMapping != null) {// 当配置了<select>标签的resultSets属性时,parentMapping的值不为nullhandleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);} else if (resultHandler == null) {// 当没有指定ResultHandler时,创建默认的DefaultResultHandlerDefaultResultHandler 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()
方法进行处理。
源码10:org.apache.ibatis.executor.resultset.DefaultResultSetHandlerpublic void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {// 判断是否有嵌套ResultMapif (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时的情况:
源码11:org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate 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实体对象。
源码12:org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate 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()
方法创建结果对象。
源码13:org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate 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实体属性的映射进行处理。
源码14:org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate 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实体属性的映射。
源码15:org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate 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。
源码16:org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate 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属性上,因此在获取该属性对应的值时,会进行嵌套查询。
源码17:org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {// 获取外部Mapper的IDfinal 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()
方法获取结果。
源码18:org.apache.ibatis.executor.loader.ResultLoaderpublic 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源码深度解析