前面一篇文章我们分析了mapper方法具体的执行流程,跟踪代码到了resultSetHandler.handleResultSets()处理结果集这里,这也是Mybatis处理数据的最后一个步骤了。
试想一下,如果让我们自己组装结果,我们该如何实现呢,可以大概想一下。
如果是我们自己实现的话,有几个关键的步骤:
那么Mybatis究竟是不是这样处理的呢,下面我们就来详细分析一下Mybatis是如何使用ResultSetHandler封装结果集的。
我们直接查看结果封装的开始入口:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
由此,进入到 resultSetHandler.handleResultSets(ps) 方法,而默认会进入到的DefaultResultSetHandler的handleResultSets方法:
//org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//存放查询结果的集合
final List<Object> multipleResults = new ArrayList<>();
//结果集的个数
int resultSetCount = 0;
//第一步:封装ResultSetWrapper
ResultSetWrapper rsw = getFirstResultSet(stmt);
//从mappedStatement中获取所有的ResultMap
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
//resultMap的个数
int resultMapCount = resultMaps.size();
// 如果结果集有数据,但是没有定义返回的结果类型,就会报错
//报错:A query was run and no Result Maps were found for the Mapped Statement.. It's likely that neither a Result Type nor a Result Map was specified.
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
//第二步:处理结果集
handleResultSet(rsw, resultMap, multipleResults, null);
//第三步:获取下一个结果集
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
//第四步:判断multipleResults结果集长度是不是等于1,如果等于1返回集合第一个元素,否则返回整个集合
return collapseSingleResultList(multipleResults);
}
上述代码有两个比较关键的地方:
下面分别对这两个步骤进行详细的介绍,先来看看ResultSetWrapper 是如何包装的,getFirstResultSet(stmt)里面主要还是调用了ResultSetWrapper的构造方法来创建一个ResultSetWrapper对象,所以我们直接看ResultSetWrapper的构造方法:
//结果集列名集合
private final List<String> columnNames = new ArrayList<>();
//结果集JavaType类型集合
private final List<String> classNames = new ArrayList<>();
//结果集JdbcType类型集合
private final List<JdbcType> jdbcTypes = new ArrayList<>();
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
super();
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.resultSet = rs;
//获取元数据
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
//挨个遍历所有的数据库列,添加到对应的集合中
for (int i = 1; i <= columnCount; i++) {
columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
classNames.add(metaData.getColumnClassName(i));
}
}
如下图,可以看到,执行完ResultSetWrapper构造方法后,几个重要的属性的值如下所示:
接下来看另外一个关键的地方:handleResultSet(rsw, resultMap, multipleResults, null):执行具体的结果集封装操作
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
//第一步:如果结果处理器为空,创建一个默认的DefaultResultHandler结果集处理器
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
//第二步:处理每一行的值
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
//第三步:将结果集添加到集合中
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
继续跟踪方法调用,接下来看一个handleRowValues()方法的源码看下如何处理每一行的值的:
//org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValues
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
//判断是否有嵌套结果集
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
//处理含有嵌套ResultMap的结果
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
//此处示例我们没有嵌套结果集,所以执行的是else逻辑
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
本示例我们没有使用到嵌套结果集,所以执行的是else非嵌套逻辑:
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
//获取ResultSet
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
//主要判断上下文是否已关闭、resultSet是否关闭以及结果集是否还有元素
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
//获取行数据
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
直接看Mybatis是如何获取一行数据的:
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//第一步:通过反射获取到需要封装的结果集实体类的构造方法,然后调用constructor.newInstance()创建一个对象
//此时返回的rowValue: User{id='null', username='null'} 里面的属性都是null
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
//行值不为空,并且结果对象有类型处理器
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
//第二步:创建元数据对象MetaObject,方便后面直接设置属性的值
//将行值包装成元数据对象MetaObject
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
//第三步:自动映射查询出来的数据到前面创建好的Java对象属性中
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
跟踪applyAutomaticMappings,看一下Mybatis是如何自动映射查询出来的数据到前面创建好的Java对象属性中的:
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
//第一步:建立好数据库列名和实体类属性名的映射关系
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
//第二步:根据mapping.column数据库列名,从查询结果集中获取到具体某一列的值
//底层实际上就是调用的TypeHandler的:rs.getString("id")、rs.getString("name")
//感兴趣的小伙伴可以查看这个方法:org.apache.ibatis.type.StringTypeHandler#getNullableResult(java.sql.ResultSet, java.lang.String)
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
//第三步:拿到值之后,那就需要动态设置属性的值为刚刚获取到的值
//通过metaObject元数据对象直接修改属性的值
// 给对象的属性设置值
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
简单总结一下applyAutomaticMappings()方法,最主要的逻辑就在这里:
如下图,Mybatis建立好的数据库列名和实体类属性名的映射关系:
如下图,可以看到,执行完第三步的时候,此时的结果集是下面这样的:
到这里,Mybatis查询结果集封装的步骤大体就完成了,接下来就是一级一级返回,添加到List结果集集合中,判断是返回一条数据还是直接返回整个结果集的集合。本文主要总结的是没有嵌套结果集的场景,感兴趣的小伙伴可以去看下嵌套结果集的处理流程,大体其实都是类似的,使用到了列名->属性映射关系,通过反射创建对象,拿到set方法,通过metaObject设置属性,无非可能会有一些集合的实例化等操作。
还是以一张流程图来总结一下查询结果集封装的过程:
本篇文章详细总结了Mybatis查询结果集封装的整个流程,包括怎么建立数据库列名和实体类属性之间的映射、反射创建ResultType实体类对象、以及如何从结果集中拿到查询值,动态通过metaObject设置到返回类型实体类属性中等等。小伙伴们下去还是需要一步一步Debug调试一下,观察各个数据的流向和处理逻辑,这样效果可能会翻倍。
鉴于笔者水平有限,如果文章有什么错误或者需要补充的,希望小伙伴们指出来,希望这篇文章对大家有帮助。