Insight Mybatis 内嵌resultMap工作原理

疑问

<!--
	如下的Mybatis 配置,collection 是如何工作的,对于SQL查询的数据结果集,在Mybatis 映射生成对象时,怎样组装的?
-->
<resultMap id="viewResultMap" type="com.mybatis.model.CityView">
	<result column="pid" jdbcType="INTEGER" property="pId"/>
    <result column="name" jdbcType="VARCHAR" property="countryName"/>
	<collection property="citys" resultMap="moreResultMap"/>
</resultMap>

Insight 总结

  1. 对于使用的ResultMap, 在查询完成后,映射处理时,会判断是否有内嵌(resultMap.hasNestedResultMaps()),从而确定执行哪种匹配映射策略。
  2. 与collection 相似的还有association 标签,都是一样的作为内嵌映射进行处理。
  3. 内嵌映射的处理思路,类似于在内存中做group by 聚合操作。以viewResultMap 的property 集合作为主键,对原始数据集进行聚合操作,数据的最终明细体现在内嵌的collection 或者 association 中。
  4. 上述的resultMap 数据结构可以用 new LinkedHashMap<K, List<V>>() 描述, Mybatis 也是基于这样的结构进行操作。
  5. Mybatis 该功能实现阅读、理解的障碍是,这个内嵌映射是个递归处理的过程,如果单纯看源码实现,就很容易陷入其中。如果能先带着猜想,通过设计思路的验证去阅读,会清晰很多。

Insight 源码分析

关键类:org.apache.ibatis.executor.resultset.DefaultResultSetHandler

/**
 * 内嵌结果集处理整体逻辑:
 * 遍历ResultSet,借助全局变量 nestedResultObjects,以主数据为全局唯一主键,处理带有内嵌配置的结果集。类似于在内存中做group by 聚合操作。
 * 具体的数据映射,对象构造,内嵌递归处理,参考 getRowValue()
 */
private void handleRowValuesForNestedResultMap() throws SQLException {
	// 遍历ResultSet
	while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
		final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
		// 构造主数据的唯一主键,这个主键会缓存在全局 nestedResultObjects
		final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
		// 根据resultSet 构造主数据,或者构造内嵌数据,追加到主数据
		rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
		// putIfAbsent 操作
		Object partialObject = nestedResultObjects.get(rowKey);
		if (partialObject == null) {
			storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
		}
	}
}
/**
 * 功能逻辑:resultSet 转化为 instance 策略实现。内嵌映射递归检测、处理。
 *
 * @partialObject 如果为null,说明是父对象的初始化过程
 * 如果非null,说明是内嵌对象的初始化过程
 */
private Object getRowValue() {
	// 内嵌对象的初始化过程
	if (partialObject != null) {
		final MetaObject metaObject = configuration.newMetaObject(rowValue);
		// 为了明确知道递归调用的阶段,需要借助ancestorObjects 来暂存对应的对象实例。递归完后remove。
		putAncestor(rowValue, resultMapId);
		applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
		ancestorObjects.remove(resultMapId);
	} else {
		// 父对象的初始化过程
		final ResultLoaderMap lazyLoader = new ResultLoaderMap();
		// 1. constructor.newInstance() 
		rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
		if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
			final MetaObject metaObject = configuration.newMetaObject(rowValue);
			boolean foundValues = this.useConstructorMappings;
			// 2. 根据resultSet 赋值给 newInstance
			foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
			putAncestor(rowValue, resultMapId);
			// 3. 检测是否有内嵌查询,如果有,则追加。递归调用,直到所有的内嵌查询查询完成。
			// 内嵌数据追加到主数据的逻辑:instantiateCollectionPropertyIfAppropriate -> 内嵌查询getRowValue() -> linkObjects
			foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
			ancestorObjects.remove(resultMapId);
			foundValues = lazyLoader.size() > 0 || foundValues;
			rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
		}
		if (combinedKey != CacheKey.NULL_CACHE_KEY) {
            // 4. 缓存rowValue,保证集合数据唯一
			nestedResultObjects.put(combinedKey, rowValue);
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值