网站做桌面应用 iOS,做网站的服务器还需要空间吗,有限责任公司注册要求,找工作平台网站文章目录 前言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源码深度解析(二十四)级联映射与关联… 文章目录 前言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 idfullUser typeUserid columnuser_id propertyuserId/result columnname propertyname/!--result columnage propertyage/--!--result columnphone propertyphone/--result columnbirthday propertybirthday/collection propertyorderListselectcom.star.mybatis.mapper.OrderMapper.listOrderByUserIdofTypeOrderjavaTypeListcolumnuser_id/collection
/resultMapage和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 idUserMap typeUserconstructoridArg columnuser_id javaTypeInteger/arg columnname javaTypeString//constructorresult columnage propertyage/result columnphone propertyphone/result columnbirthday propertybirthday/
/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.ResultMappublic class ResultMap {private Configuration configuration;// resultMap标签的id属性private String id;// resultMap标签的type属性指定与数据库表建立映射关系的Java实体private Class? type;// result标签配置的映射信息private ListResultMapping resultMappings;// id标签配置的主键映射信息private ListResultMapping idResultMappings;// constructor标签配置的构造器映射信息private ListResultMapping constructorResultMappings;// result标签配置的结果集映射信息private ListResultMapping propertyResultMappings;// 存放所有映射的数据库字段信息private SetString mappedColumns;// 存放所有映射的属性信息private SetString mappedProperties;// discriminator标签配置的鉴别器信息private Discriminator discriminator;// 是否有嵌套的resultMapprivate 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.XMLMapperBuilderpublic 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.XMLMapperBuilderprivate void configurationElement(XNode context) {try {// 获取和配置命名空间String namespace context.getStringAttribute(namespace);if (namespace null || namespace.isEmpty()) {throw new BuilderException(Mappers 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.XMLMapperBuilderprivate void resultMapElements(ListXNode 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, ListResultMapping 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;ListResultMapping resultMappings new ArrayList(additionalResultMappings);// 获取resultMap标签的子标签ListXNode 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 {// 处理其他标签ListResultFlag 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.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 ListResultMapping resultMappings;private final Boolean autoMapping;public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class? type, String extend,Discriminator discriminator, ListResultMapping 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.MapperBuilderAssistantpublic ResultMap addResultMap(String id, Class? type, String extend, Discriminator discriminator,ListResultMapping 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.PreparedStatementHandlerOverride
public E ListE 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.DefaultResultSetHandlerOverride
public ListObject handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity(handling results).object(mappedStatement.getId());final ListObject multipleResults new ArrayList();int resultSetCount 0;// 获取第一个结果集并将其包装成ResultSetWrapperResultSetWrapper rsw getFirstResultSet(stmt);// 获取本次查询对应的ResultMap对象ListResultMap 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.DefaultResultSetHandlerprivate void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, ListObject 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()方法进行处理。
源码10org.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时的情况
源码11org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,ResultHandler? resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {DefaultResultContextObject 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.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()方法做了三件事情
1getRowValue()方法的第一步调用createResultObject()方法创建结果对象。
源码13org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ListClass? constructorArgTypes,ListObject 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注解的构造方法】的顺序找到结果对象的构造方法通过构造方法创建一个结果对象。
2getRowValue()方法的第二步调用applyAutomaticMappings()方法处理自动映射对未通过result等标签配置的数据库字段与Java实体属性的映射进行处理。
源码14org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,String columnPrefix) throws SQLException {// 获取需要进行自动映射的UnMappedColumnAutoMapping对象// UnMappedColumnAutoMapping封装了数据库字段与Java实体属性的关联关系ListUnMappedColumnAutoMapping 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属性已被成功赋值。
3getRowValue()方法的第三步调用applyPropertyMappings()方法处理通过result等标签配置的数据库字段与Java实体属性的映射。
源码15org.apache.ibatis.executor.resultset.DefaultResultSetHandlerprivate boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {final SetString mappedColumnNames rsw.getMappedColumnNames(resultMap, columnPrefix);boolean foundValues false;// 获取通过标签配置的映射集合final ListResultMapping 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.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属性上因此在获取该属性对应的值时会进行嵌套查询。 源码17org.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()方法获取结果。
源码18org.apache.ibatis.executor.loader.ResultLoaderpublic Object loadResult() throws SQLException {ListObject list selectList();resultObject resultExtractor.extractObjectFromList(list, targetType);return resultObject;
}private E ListE 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源码深度解析