*************************************优雅的分割线 **********************************
分享一波:程序员赚外快-必看的巅峰干货
如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程
请关注微信公众号: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荷包
一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************