Mybatis源码分析之结果集处理

解析封装

ResultMap 是和结果集相关的东西,最初在解析 XML 的时候,于 parseStatementNode 方法中,针对每一个 select 节点进行解析,转换为 MappedStatement(类似 Spring 的 bean 配置和 BeanDefinition 的关系)。

在 MapperBuilderAssistant 的 addMappedStatement 方法中,构建完statementBuilder,会调用 setStatementResultMap 方法给其设置 ResultMap。

setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);

private void setStatementResultMap(
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
MappedStatement.Builder statementBuilder) {
resultMap = applyCurrentNamespace(resultMap, true); List<ResultMap> resultMaps = new ArrayList<ResultMap>();
if (resultMap != null) {
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
try {
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("Could not find result map " + resultMapName, e);
}
}
} else if (resultType != null) {
ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
configuration,
statementBuilder.id() + "-Inline",
resultType,
new ArrayList<ResultMapping>(),
null);
resultMaps.add(inlineResultMapBuilder.build());
}
statementBuilder.resultMaps(resultMaps); statementBuilder.resultSetType(resultSetType);
}

首先检查 resultMap,根据名称去 configuration 中取出对应的 resultMap 放到集合中;

如果 resultMap 不存在,就检查 resultType ,然后利用这个 resultType 构造一个 resultMap,如果两个都没有,那就走着瞧(resultMaps集合为空)。

中期调用

在 PreparedStatementHandler 的 query 方法中:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}

而 resultSetHandler 于基类 BaseStatementHandler 中构造:

this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

根据是否有嵌套的 ResultMaps 来确定创建 NestedResultSetHandler 还是 FastResultSetHandler:

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ? new NestedResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql,
rowBounds) : new FastResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}

影响这个判断的有几个点:

  • 解析时期
public Builder resultMaps(List<ResultMap> resultMaps) {
mappedStatement.resultMaps = resultMaps;
for (ResultMap resultMap : resultMaps) {
mappedStatement.hasNestedResultMaps = mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps();
}
return this;
}
  • getBoundSql时期
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}

后期处理

NestedResultSetHandler继承自FastResultSetHandler ,二者在handleResultSets是一致的(只是重写了 handleRowValues 等方法):

public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<Object>();
final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
int resultSetCount = 0;
ResultSet rs = stmt.getResultSet(); while (rs == null) {
// move forward to get the first resultset in case the driver
// doesn't return the resultset as the first result (HSQLDB 2.1)
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else {
if (stmt.getUpdateCount() == -1) {
// no more results. Must be no resultset
break;
}
}
}
// 验证是否定义了resultType或者resultMap
validateResultMapsCount(rs, resultMapCount);
while (rs != null && resultMapCount > resultSetCount) {
final ResultMap resultMap = resultMaps.get(resultSetCount);
ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
rs = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
return collapseSingleResultList(multipleResults);
}

处理模板

  • validateResultMapsCount

resultMapCount 就是之前解析时构造的那个 ResultMap 集合的 size,如果没有配置 ResultMap 或 ResultType,这里就要报错了:

A query was run and no Result Maps were found for the Mapped Statement ……

  • handleResultSet

传入构建的List类型的集合 multipleResults,调用 handleRowValues 处理从 ResultSet 获取的结果集。

  • collapseSingleResultList

这个就不说了。

细节分析

FastResultSetHandler 用得相对较多,这里只针对其 handleRowValues 处理结果行进行分析:

protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
final DefaultResultContext resultContext = new DefaultResultContext();
skipRows(rs, rowBounds);
while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
callResultHandler(resultHandler, resultContext, rowValue);
}
}

1、RowBounds 与 分页

skipRows 就是跳过行,基于查询结果的分页使用,其中会直接调用 ResultSet 的 absolute 方法:

rs.absolute(rowBounds.getOffset());

而 shouldProcessMoreRows 方法就是判断是否应该获取更多的行:

protected boolean shouldProcessMoreRows(ResultSet rs, ResultContext context, RowBounds rowBounds) throws SQLException {
return !context.isStopped() && rs.next() && context.getResultCount() < rowBounds.getLimit();
}

也对应了分页的limit字段,这种分页可以说是伪分页,查出来再分页。所以我们一般使用插件的形式来实现分页,基于sql的动态替换。

2、获取行数据

如果应该获取更多的行,就会逐行处理结果:

protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(resultObject);
boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
foundValues = applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache) || foundValues;
}
final List<String> mappedColumnNames = resultColumnCache.getMappedColumnNames(resultMap, null);
foundValues = applyPropertyMappings(rs, resultMap, mappedColumnNames, metaObject, lazyLoader, null) || foundValues;
foundValues = (lazyLoader != null && lazyLoader.size() > 0) || foundValues;
resultObject = foundValues ? resultObject : null;
return resultObject;
}
return resultObject;
}

主要就是根据相关参数创建结果对象,这里又分多钟情况:

  • 原始对象的处理(如 Integer 等基本类型对象)

利用 ResultMap 中存储的返回结果类型信息,直接返回相应的值。

  • 非原始对象(是自定义的对象)处理

运用反射构造实体对象(相当于一个带有零值的初始对象)。然后用该对象构造一个 MetaObject ,并查看是否需要根据映射自动填充对象。

如果需要通过自动映射来填充,取出 List 类型的属性名,根据这个同 SQL 中查出来一致的字段名,去 ResultSet 中拿出来然后填充到对象中,相关代码如下:

final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
if (property != null) {
final Class<?> propertyType = metaObject.getSetterType(property);
if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
final TypeHandler<?> typeHandler = resultColumnCache.getTypeHandler(propertyType, columnName);
final Object value = typeHandler.getResult(rs, columnName);
if (value != null || configuration.isCallSettersOnNulls()) { // issue #377, call setter on nulls
metaObject.setValue(property, value);
foundValues = true;
}
}
}

这个处理完后,才是非映射列的处理。

3、结果映射

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来, 并在一些情形下允许你做一些 JDBC 不支持的事情。

在 handleResultSets 方法中,取 ResultSet 元信息构造了一个 ResultColumnCache:

ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);

后面在映射列的时候分别调用了 getUnmappedColumnNames 和 getMappedColumnNames 方法来取列集合,对应于 ResultColumnCache 的 unMappedColumnNamesMap 和 mappedColumnNamesMap。如果集合为空,将调用下面的方法来构造:

private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
List<String> mappedColumnNames = new ArrayList<String>();
List<String> unmappedColumnNames = new ArrayList<String>();
final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
for (String columnName : columnNames) {
final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
if (mappedColumns.contains(upperColumnName)) {
mappedColumnNames.add(upperColumnName);
} else {
unmappedColumnNames.add(columnName);
}
}
mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}

通常情况下,loadMappedAndUnmappedColumnNames 只会调用一次(多行结果的列名是一样的),所以 ResultColumnCache 名副其实。

它区分是否是满足映射的列名是根据 mappedColumns 集合中是否有来判断的,而 mappedColumns 对应于 ResultMap 配置中的 column 属性。

所以自动映射针对的是没有配置 ResultMap 而使用了 ResultType 的情况;或者配置并使用了 ResultMap,但是 ResultMap 中的 column 属性集合不包含实际SQL字段名,也就是对不上的情况。这个时候是需要直接操作这个字段名,可能需要去除下划线等基于规则的演变,最后通过 MetaObject 来赋值。

那么非自动映射呢?就是配置并使用了 ResultMap,这个简单多了,直接按你配置的来,取 ResultMap 属性配置下的 property 名称,这个就是你实际的对象属性名,然后也是通过 MetaObject 来赋值。

分析到这里,已经比较明朗了,整体的脉络也在脑海中勾勒出来了。ResultMap 实际远不止这些,至于高级功能到时候用起来再具体分析吧。

上一篇:Java 和 Javascript的关系


下一篇:IBatisNet基础组件