*************************************优雅的分割线 **********************************

分享一波:程序员赚外快-必看的巅峰干货

如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程

请关注微信公众号:HB荷包

一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************
前言

这段时间疫情原因躺在家做咸鱼,代码也没怎么敲,源码也没怎么看,博客拖更了一个月,今天心血来潮继续读了点源码,晚上正好抽空发个博客,证明我还活着。

关于结果集映射,在一个月前的博客中已经将简单映射给讲述完毕,在实际应用中,除了单表查询以外,还可能通过连表查询多张表的记录,这些记录需要映射成多个java对象,而对象之间存在一对一、一对多等复杂的关联关系,这时候就需要嵌套映射。
handleRowValues

在前面的一篇博客中提到,结果集映射的核心方法是handleRowValues,在这个方法中,会先判断ResultMap是否存在嵌套映射,如不存在就视为简单结果集映射,简单映射的处理在上一篇博客已经讲解完毕,本篇博客讲述的是嵌套映射

/*** 结果集映射核心方法** @param rsw* @param resultMap* @param resultHandler* @param rowBounds* @param parentMapping* @throws SQLException*/
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {if (resultMap.hasNestedResultMaps()) {ensureNoRowBounds();checkResultHandler();// 嵌套映射handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);} else {// 简单结果集映射(单表)handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);}
}

handleRowValuesForNestedResultMap

该方法是处理嵌套映射的核心方法,有以下主要步骤

通过skipRows方法定位到指定的记录行
通过shouldProcessMoreRows方法检测是否能够继续映射结果集中剩余的记录行
调用resolveDiscriminatedResultMap方法,根据ResultMap中记录的Discriminator对象以及参与映射的记录行中相应的列值,决定映射使用的ResultMap对象。
通过createRowKey方法为该行记录生成CacheKey,CacheKey作为缓存中的key值,同时在嵌套映射中也作为key唯一标识一个结果集对象。
根据上面步骤生成的CacheKey查询DefaultRe.nestedResultObjects集合,这个字段是一个HashMap,在处理嵌套映射过程中生成的所有结果对象,都会生成相应的CacheKey并保存到该集合。
检测<select>节点中resultOrdered属性的配置,该设置仅对嵌套映射有效。当Ordered属性为true时,则认为返回一个主结果行
通过getRowValue,完成当前记录行的映射操作并返回结果对象,其中还会讲结果对象添加到nestedResultObjects集合中。
通过storeObject方法将生成的结果对象保存在ResultHandler中。

handleRowValuesForNestedResultMap方法代码如下。、

/*** 处理嵌套映射* @param rsw* @param resultMap* @param resultHandler* @param rowBounds* @param parentMapping* @throws SQLException*/
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {final DefaultResultContext<Object>  resultContext = new DefaultResultContext<>();// 获取结果集ResultSet resultSet = rsw.getResultSet();// 定位到指定的行skipRows(resultSet, rowBounds);Object rowValue = previousRowValue;// 检测在定位到指定行之后,是否还有需要映射的数据while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {// 得到本次查询使用的ResultMapfinal ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);// 为该行记录生成CacheKey,作为缓存中的key值final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);// 根据缓存key先获取映射缓存Object partialObject = nestedResultObjects.get(rowKey);// 检测select节点中的resultOrder属性。该属性只针对嵌套映射有效。// 当true时则认为返回一个主结果行时,不会记录nestedResultObjectif (mappedStatement.isResultOrdered()) {// 主结果对象发生变化if (partialObject == null && rowValue != null) {// 清空缓存集合nestedResultObjects.clear();// 保存主结果对象storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}// 获取映射结果rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);} else {rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);if (partialObject == null) {// 将生成结果保存到ResultHandlerstoreObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}}}if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);previousRowValue = null;} else if (rowValue != null) {previousRowValue = rowValue;}
}

前面一部分代码的分析在简单映射中已经描述过,不记得的朋友可以查看一下上一篇源码阅读文章,这里从createRowKey方法开始。
createRowKey

createRowKey方法主要负责生成CacheKey,该方法构建CacheKey的过程如下。

尝试使用<idArg>节点或者<id>节点中定义的列名以及该列在当前记录行中对应的列值生成CacheKey
如果ResultMap中没有定义这两个节点,则有ResultMap中明确要映射的列名以及它们在当前记录行中对应的列值一起构成CacheKey对象
经过上面两个步骤后如果依然查不到相关的列名和列值,且ResultMap的type属性明确指明了结果对象为Map类型,则有结果集中所有列名以及改行记录行的所有列值一起构成CacheKey
如果映射的结果对象不是Map,则由结果集中未映射的列名以及它们在当前记录行中的对应列值一起构成CacheKey

createRowKey代码如下

/*** 创建一个CacheKey,作为缓存中的key值,在嵌套映射中也作为key唯一标识一个结果对象* @param resultMap* @param rsw* @param columnPrefix* @return* @throws SQLException*/
private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {final CacheKey cacheKey = new CacheKey();// 将resultMap的id属性作为CacheKey的一部分cacheKey.update(resultMap.getId());// 查找ResultMapping集合List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);// 没找到if (resultMappings.isEmpty()) {if (Map.class.isAssignableFrom(resultMap.getType())) {// 由结果集中的所有列名以及当前记录行的所有列值一起构成CacheKeycreateRowKeyForMap(rsw, cacheKey);} else {// 由结果集中未映射的列名以及它们在当前记录行中的对应列值一起构成CacheKey对象createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);}} else {// 由ResultMapping集合中的列名以及它们在当前记录行中相应的列值一起构成CacheKeycreateRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);}// 如果在上面的过程没有找到任何列参与构成CacheKey对象,则返回NullCacheKeyif (cacheKey.getUpdateCount() < 2) {return CacheKey.NULL_CACHE_KEY;}return cacheKey;
}

其中,getResultMappingsForRowKey方法首先检查ResultMap中是否定义了idArg或者id节点,如果是则返回idResultMappings集合,否则返回propertyResultMappings集合

/*** 获取ResultMapping集合* @param resultMap* @return*/
private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {//首先检查resultMap中是否定义了idArg节点或者id节点List<ResultMapping> resultMappings = resultMap.getIdResultMappings();if (resultMappings.isEmpty()) {// propertyResultMappings集合记录了除id和constructor节点以外的ResultMapping对象resultMappings = resultMap.getPropertyResultMappings();}return resultMappings;
}

createRowKeyForMap、createRowKeyForUnmappedProperties和createRowKeyForMappedProperties三个方法核心逻辑都是通过CacheKey的update方法,将指定的列名以及它们在当前记录行中相应的列值添加到CacheKey,使之成为CacheKey对象的一部分。

这里只介绍createRowKeyForMappedProperties

/*** 核心逻辑是通过CacheKey.update方法,将指定的列名以及它们在当前记录行中相应的列值添加到CacheKey* @param resultMap* @param rsw* @param cacheKey* @param resultMappings* @param columnPrefix* @throws SQLException*/
private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {for (ResultMapping resultMapping : resultMappings) {// 如果存在嵌套映射,并且resultSet不为空if (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null) {// 如果存在嵌套映射,递归调用该方法处理final ResultMap nestedResultMap = configuration.getResultMap(resultMapping.getNestedResultMapId());createRowKeyForMappedProperties(nestedResultMap, rsw, cacheKey, nestedResultMap.getConstructorResultMappings(),prependPrefix(resultMapping.getColumnPrefix(), columnPrefix));} else if (resultMapping.getNestedQueryId() == null) {// 忽略嵌套查询// 获取该列名称final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);// 获取该列相应的TypeHandlerfinal TypeHandler<?> th = resultMapping.getTypeHandler();// 获取映射的列名List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {// 获取列值final Object value = th.getResult(rsw.getResultSet(), column);if (value != null || configuration.isReturnInstanceForEmptyRow()) {// 将列值和列名添加到CacheKey中cacheKey.update(column);cacheKey.update(value);}}}}
}

getRowValue方法

getRowValue方法主要负责对数据集中的一行记录进行映射。在处理嵌套映射的过程中,会调用getRowValue方法,完成对记录行的映射,步骤如下。

检测外层对象是否已经存在

如果外层对象不存在

调用createRowObject方法创建外层对象
将外层对象添加到DefaultResultSetHandler.ancestorObject集合中,其中key是ResultMap的id,value为外层对象。
通过通过applyNestedResultMappings方法处理嵌套映射,其中会将生成的结果对象设置到外层对象的相应的属性中。
将外层的ancestorObject集合中移除
将外层对象保存到nestedResultObjects集合中。

如果外层对象已存在

将外层对象添加到ancestorObjects集合中
通过applyNestedResultMappings方法处理嵌套映射,其中会将生成的结果对象设置到外层对象的相应属性中
将外层对象从ancestorObjects集合中移除。

getRowValue方法代码如下

/*** 完成对嵌套查询记录的映射* @param rsw* @param resultMap* @param combinedKey* @param columnPrefix* @param partialObject* @return* @throws SQLException*/
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {final String resultMapId = resultMap.getId();Object rowValue = partialObject;if (rowValue != null) {// 外层对象存在final MetaObject metaObject = configuration.newMetaObject(rowValue);// 将外层对象添加到ancestorObjectsputAncestor(rowValue, resultMapId);// 处理嵌套映射,其中会将生成的结果对象设置到外层对象的相应属性中applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);// 将外层对象从ancestorObjects移除ancestorObjects.remove(resultMapId);} else {// 外层对象不存在final ResultLoaderMap lazyLoader = new ResultLoaderMap();// 创建外层对象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, true)) {// 自动映射foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}// 处理ResultMap找那个明确需要映射的列foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;putAncestor(rowValue, resultMapId);// 处理嵌套映射,将生成的结果对象设置到外层对象的相应的属性中foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;// 将外层对象从ancestorObjects集合中移除ancestorObjects.remove(resultMapId);foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}if (combinedKey != CacheKey.NULL_CACHE_KEY) {// 将外层对象添加到nestedResultObjectsnestedResultObjects.put(combinedKey, rowValue);}}return rowValue;
}

applyNestedResultMappings方法

处理嵌套逻辑的核心在这个方法中,该方法会遍历ResultMap.propertyResultMappings集合中记录的ResultMapping对象,并处理其中的嵌套映射。该方法步骤如下。

获取ResultMapping.nestedResultMapId字段值,该值不为空则表示存在相应的嵌套映射要处理。同时还会检测ResultMapping.resultSet字段,它指定了要映射的结果及名称,该属性的映射在前面的handleResultSets方法中完成。
通过resolveDiscriminatedResultMap方法确定嵌套映射使用的ResultMap对象
处理循环引用的场景,如果不存在循环引用的情况,则继续后面的映射流程。如果存在循环引用,则不在创建新的对象,而是重用前面的对象
通过createRowKey方法为嵌套对象创建CacheKey。该过程除了根据嵌套对象的信息创建CacheKey,还会与外层对象的CacheKey合并,得到全局唯一的CacheKey
如果外层对象中用于记录当前嵌套对象的属性为Collection并且未初始化,则会通过instantiateCollectionPropertyIfAppropriate方法初始化该对象
根据association、collection等节点的notNullColumn属性,检测结果集中相应的列是否为空
调用getRowValue方法完成嵌套映射,并生成嵌套对象。嵌套对象可以嵌套多层,也就可以产生多层递归。
通过linkObjects方法,将上一步骤得到的嵌套对象保存到外层对象。

applyNestedResultMappings方法代码如下

/*** 处理嵌套映射的核心代码* @param rsw* @param resultMap* @param metaObject* @param parentPrefix* @param parentRowKey* @param newObject* @return*/
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {boolean foundValues = false;for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {// 获取引用其他的ResultMap的idfinal String nestedResultMapId = resultMapping.getNestedResultMapId();// 如果指定了嵌套映射的id,并且尚未映射if (nestedResultMapId != null && resultMapping.getResultSet() == null) {try {// 获取列前缀final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);// 根据上面获取到的嵌套映射id去从配置中找到对应的ResultMapfinal ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);// 列前缀为空的情况下处理,一般不去用if (resultMapping.getColumnPrefix() == null) {Object ancestorObject = ancestorObjects.get(nestedResultMapId);if (ancestorObject != null) {if (newObject) {linkObjects(metaObject, resultMapping, ancestorObject);}continue;}}// 为嵌套对象创建CacheKey,该过程创建的CacheKey还会与外层对象的CacheKey合并final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);// 合并CacheKeyfinal CacheKey combinedKey = combineKeys(rowKey, parentRowKey);Object rowValue = nestedResultObjects.get(combinedKey);boolean knownValue = rowValue != null;// 如果嵌套对象是集合,并且没有初始化,会调用该方法对其进行初始化instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);// 根据notNullColumn属性,检测结果集中相应的列是否为空if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {// 获取映射结果rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);if (rowValue != null && !knownValue) {linkObjects(metaObject, resultMapping, rowValue);foundValues = true;}}} catch (SQLException e) {throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);}}}return foundValues;
}

结语

距离上一篇源码分析的博客已经间隔了一个多月,最近在家闲够了就着手继续写博客了,关于这块的内容不会弃坑,只是偶尔会拖更一下下。。

*************************************优雅的分割线 **********************************

分享一波:程序员赚外快-必看的巅峰干货

如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程

请关注微信公众号:HB荷包

一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************

Mybatis源码阅读(三):结果集映射3.2 —— 嵌套映射相关推荐

  1. mybatis源码阅读(三):mybatis初始化(下)mapper解析

    转载自 mybatis源码阅读(三):mybatis初始化(下)mapper解析 MyBatis 的真正强大在于它的映射语句,也是它的魔力所在.由于它的异常强大,映射器的 XML 文件就显得相对简单. ...

  2. mybatis源码阅读(八) ---Interceptor了解一下

    转载自  mybatis源码阅读(八) ---Interceptor了解一下 1 Intercetor MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis允许 ...

  3. mybatis源码阅读(五) ---执行器Executor

    转载自  mybatis源码阅读(五) ---执行器Executor 1. Executor接口设计与类结构图 public interface Executor {ResultHandler NO_ ...

  4. mybatis源码阅读(二):mybatis初始化上

    转载自  mybatis源码阅读(二):mybatis初始化上 1.初始化入口 //Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与 ...

  5. mybatis源码阅读(一):SqlSession和SqlSessionFactory

    转载自  mybatis源码阅读(一):SqlSession和SqlSessionFactory 一.接口定义 听名字就知道这里使用了工厂方法模式,SqlSessionFactory负责创建SqlSe ...

  6. Mybatis源码阅读(一):Mybatis初始化1.1 解析properties、settings

    *************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...

  7. Mybatis源码阅读之二——模板方法模式与Executor

    [系列目录] Mybatis源码阅读之一--工厂模式与SqlSessionFactory 文章目录 一. 模板方法模式 二. 同步回调与匿名函数 三. Executor BaseExecutor与其子 ...

  8. Mybatis 源码阅读环境搭建

    Mybatis源码阅读环境搭建 前言 一.下载mybatis的源码 二.编译源码 三.创建测试项目 前言     mybatis源码阅读环境搭建还是比较简单的,接下来我们讲解一下如何搭建该源码阅读环境 ...

  9. 24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment

    24 UsageEnvironment使用环境抽象基类--Live555源码阅读(三)UsageEnvironment 24 UsageEnvironment使用环境抽象基类--Live555源码阅读 ...

  10. mybatis源码阅读(七) ---ResultSetHandler了解一下

    转载自  mybatis源码阅读(七) ---ResultSetHandler了解一下 1.MetaObject MetaObject用于反射创建对象.反射从对象中获取属性值.反射给对象设置属性值,参 ...

最新文章

  1. 计算机组成与体系结构-----数制
  2. HDU5511 : Minimum Cut-Cut
  3. 这台无人机40小时经历上万次事故,终于借助AI学会了自动飞行
  4. LeetCode Algorithm 451. 根据字符出现频率排序
  5. [转载]析构函数的虚析构和非虚析构调用的差别
  6. python打包exe报错编码问题,使用Python打包含有pymssql成exe所躺的坑
  7. DHCP+VTP(实验讲解+配置)
  8. 软件测试(一)-黑盒测试 随机测试技巧
  9. 计算机加经济学加自动化,MIT经济学家戳破机器人真相:除了能取代你,价值微乎其微...
  10. win11怎么安装安卓app win11上安装安卓app的步骤教程
  11. 类和对象编程(三):构造函数析构函数
  12. 比特币科普:区块链技术神奇在哪里?
  13. 使用Kmeans聚类分析对复杂的数据进行分类
  14. 【火灾检测】基于matlab GUI火灾检测(带面板)【含Matlab源码 1646期】
  15. 用java语言写一个实用小程序_用java语言,编写一个小程序。
  16. javascript手册安卓版_JavaScript 手册
  17. 计算机中的前端和后端岗位,前端工程师的岗位职责
  18. pycharm安装open3d的方法
  19. markdown/md文件只读权限修改
  20. CPT103-Introduction to Databases小豪的笔记

热门文章

  1. nedc和epa续航里程什么意思_400公里已成续航新起点,纯电动车的实用性到底怎样...
  2. 《软件项目管理(第二版)》第 3 章——项目计划 重点部分总结
  3. MySQL 数据库图形化管理界面应用种草之 Navicat Premium 如何使用
  4. python设计一个动物类_我用Python实现了12500张猫狗图像的精准分类
  5. 监控linux时间不对,shell 计算故障时间 配合web监控
  6. mysql 查询表的key_mysql查询表和字段的注释
  7. python现有两个磁盘文件a和b_有两个磁盘文件A和B,各存放一行字母,今要求把这两个文件中的信息合并,输出到一个新文件C中。...
  8. 单片机c语言编程要点,第1章单片机的C语言编程_2015要点.ppt
  9. Java线程怎么发送消息_Java客户端Socket如何能在阻塞线程下收到服务端发送来的消息?...
  10. php yii2 sns,GitHub - yggphpcoder/iisns: 基于 yii2 的 sns 社区系统,一站式解决社区建站...