线上广告代理平台,网站网络排名优化方法,公司取名字大全免费查询2022,淮南网站建设费用*************************************优雅的分割线 **********************************
分享一波:程序员赚外快-必看的巅峰干货
如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程
请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…*************************************优雅的分割线 **********************************
分享一波:程序员赚外快-必看的巅峰干货
如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程
请关注微信公众号: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方法为该行记录生成CacheKeyCacheKey作为缓存中的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 DefaultResultContextObject 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则由结果集中未映射的列名以及它们在当前记录行中的对应列值一起构成CacheKeycreateRowKey代码如下
/*** 创建一个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集合ListResultMapping 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 ListResultMapping getResultMappingsForRowKey(ResultMap resultMap) {//首先检查resultMap中是否定义了idArg节点或者id节点ListResultMapping 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, ListResultMapping 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();// 获取映射的列名ListString 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的idvalue为外层对象。
通过通过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荷包 一个能让你学习技术和赚钱方法的公众号,持续更新 *************************************优雅的分割线 **********************************