code-learning/mybatis/24-mybatis-SQL 执行(四)之 ResultSetHandler.md

2285 lines
96 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 精尽 MyBatis 源码分析 —— SQL 执行(四)之 ResultSetHandler
# 1. 概述
本文,我们来分享 SQL 执行的第四部分SQL 执行后,响应的结果集 ResultSet 的处理,涉及 `executor/resultset``executor/result``cursor` 包。整体类图如下:[![类图](24-mybatis-SQL 执行(四)之 ResultSetHandler.assets/01.png)](http://static.iocoder.cn/images/MyBatis/2020_03_09/01.png)类图
- 核心类是 ResultSetHandler 接口及其实现类 DefaultResultSetHandler 。在它的代码逻辑中,会调用类图中的其它类,实现将查询结果的 ResultSet ,转换成映射的对应结果。
# 2. ResultSetWrapper
> 老艿艿:在看具体的 DefaultResultSetHandler 的实现代码之前,我们先看看 ResultSetWrapper 的代码。因为 DefaultResultSetHandler 对 ResultSetWrapper 的调用比较多,避免混着解析。
`org.apache.ibatis.executor.resultset.ResultSetWrapper` `java.sql.ResultSet` 的 包装器,可以理解成 ResultSet 的工具类,提供给 DefaultResultSetHandler 使用。
## 2.1 构造方法
```
// ResultSetWrapper.java
/**
* ResultSet 对象
*/
private final ResultSet resultSet;
private final TypeHandlerRegistry typeHandlerRegistry;
/**
* 字段的名字的数组
*/
private final List<String> columnNames = new ArrayList<>();
/**
* 字段的 Java Type 的数组
*/
private final List<String> classNames = new ArrayList<>();
/**
* 字段的 JdbcType 的数组
*/
private final List<JdbcType> jdbcTypes = new ArrayList<>();
private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.resultSet = rs;
// <1> 遍历 ResultSetMetaData 的字段们,解析出 columnNames、jdbcTypes、classNames 属性
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));
}
}
```
- `resultSet` 属性,被包装的 ResultSet 对象。
- `columnNames``classNames``jdbcTypes` 属性,在 `<1>` 处,通过遍历 ResultSetMetaData 的字段们,从而解析出来。
## 2.2 getTypeHandler
```
// ResultSetWrapper.java
/**
* TypeHandler 的映射
*
* KEY1字段的名字
* KEY2Java 属性类型
*/
private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
/**
* Gets the type handler to use when reading the result set.
* Tries to get from the TypeHandlerRegistry by searching for the property type.
* If not found it gets the column JDBC type and tries to get a handler for it.
*
* 获得指定字段名的指定 JavaType 类型的 TypeHandler 对象
*
* @param propertyType JavaType
* @param columnName 执行字段
* @return TypeHandler 对象
*/
public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
TypeHandler<?> handler = null;
// <1> 先从缓存的 typeHandlerMap 中,获得指定字段名的指定 JavaType 类型的 TypeHandler 对象
Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
if (columnHandlers == null) {
columnHandlers = new HashMap<>();
typeHandlerMap.put(columnName, columnHandlers);
} else {
handler = columnHandlers.get(propertyType);
}
// <2> 如果获取不到,则进行查找
if (handler == null) {
// <2> 获得 JdbcType 类型
JdbcType jdbcType = getJdbcType(columnName);
// <2> 获得 TypeHandler 对象
handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
// Replicate logic of UnknownTypeHandler#resolveTypeHandler
// See issue #59 comment 10
// <3> 如果获取不到,则再次进行查找
if (handler == null || handler instanceof UnknownTypeHandler) {
// <3> 使用 classNames 中的类型,进行继续查找 TypeHandler 对象
final int index = columnNames.indexOf(columnName);
final Class<?> javaType = resolveClass(classNames.get(index));
if (javaType != null && jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
} else if (javaType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType);
} else if (jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(jdbcType);
}
}
// <4> 如果获取不到,则使用 ObjectTypeHandler 对象
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = new ObjectTypeHandler();
}
// <5> 缓存到 typeHandlerMap 中
columnHandlers.put(propertyType, handler);
}
return handler;
}
```
- `<1>` 处,先从缓存的 `typeHandlerMap` 中,获得指定字段名的指定 JavaType 类型的 TypeHandler 对象。
- `<2>` 处,如果获取不到,则基于 `propertyType` + `jdbcType` 进行查找。其中,`#getJdbcType(String columnName)` 方法,获得 JdbcType 类型。代码如下:
```
// ResultSetWrapper.java
public JdbcType getJdbcType(String columnName) {
for (int i = 0; i < columnNames.size(); i++) {
if (columnNames.get(i).equalsIgnoreCase(columnName)) {
return jdbcTypes.get(i);
}
}
return null;
}
```
- 通过 `columnNames` 索引到位置 `i` ,从而到 `jdbcTypes` 中获得 JdbcType 类型。
- `<3>` 处,如果获取不到,则基于 `javaType` + `jdbcType` 进行查找。其中,`javaType` 使用 `classNames` 中的类型。而 `#resolveClass(String className)` 方法,获得对应的类。代码如下:
```
// ResultSetWrapper.java
private Class<?> resolveClass(String className) {
try {
// #699 className could be null
if (className != null) {
return Resources.classForName(className);
}
} catch (ClassNotFoundException e) {
// ignore
}
return null;
}
```
- `<4>` 处,如果获取不到,则使用 ObjectTypeHandler 对象。
- `<5>` 处,缓存 TypeHandler 对象,到 `typeHandlerMap` 中。
## 2.3 loadMappedAndUnmappedColumnNames
`#loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix)` 方法,初始化**有 mapped** 和**无 mapped**的字段的名字数组。代码如下:
```
// ResultSetWrapper.java
/**
* 有 mapped 的字段的名字的映射
*
* KEY{@link #getMapKey(ResultMap, String)}
* VALUE字段的名字的数组
*/
private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
/**
* 无 mapped 的字段的名字的映射
*
* 和 {@link #mappedColumnNamesMap} 相反
*/
private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();
private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
List<String> mappedColumnNames = new ArrayList<>();
List<String> unmappedColumnNames = new ArrayList<>();
// <1> 将 columnPrefix 转换成大写,并拼接到 resultMap.mappedColumns 属性上
final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
// <2> 遍历 columnNames 数组,根据是否在 mappedColumns 中,分别添加到 mappedColumnNames 和 unmappedColumnNames 中
for (String columnName : columnNames) {
final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
if (mappedColumns.contains(upperColumnName)) {
mappedColumnNames.add(upperColumnName);
} else {
unmappedColumnNames.add(columnName);
}
}
// <3> 将 mappedColumnNames 和 unmappedColumnNames 结果,添加到 mappedColumnNamesMap 和 unMappedColumnNamesMap 中
mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}
```
- `<1>` 处,将 `columnPrefix` 转换成大写,后调用 `#prependPrefixes(Set<String> columnNames, String prefix)` 方法,拼接到 `resultMap.mappedColumns` 属性上。代码如下:
```
// ResultSetWrapper.java
private Set<String> prependPrefixes(Set<String> columnNames, String prefix) {
// 直接返回 columnNames ,如果符合如下任一情况
if (columnNames == null || columnNames.isEmpty() || prefix == null || prefix.length() == 0) {
return columnNames;
}
// 拼接前缀 prefix ,然后返回
final Set<String> prefixed = new HashSet<>();
for (String columnName : columnNames) {
prefixed.add(prefix + columnName);
}
return prefixed;
}
```
- 当然,可能有胖友,跟我会懵逼,可能已经忘记什么是 `resultMap.mappedColumns` 。我们来举个示例:
```
<resultMap id="B" type="Object">
<result property="year" column="year"/>
</resultMap>
<select id="testResultMap" parameterType="Integer" resultMap="A">
SELECT * FROM subject
</select>
```
- 此处的 `column="year"` ,就会被添加到 `resultMap.mappedColumns` 属性上。
- `<2>` 处,遍历 `columnNames` 数组,根据是否在 `mappedColumns` 中,分别添加到 `mappedColumnNames` 和 `unmappedColumnNames` 中。
- `<3>` 处,将 `mappedColumnNames` 和 `unmappedColumnNames` 结果,添加到 `mappedColumnNamesMap` 和 `unMappedColumnNamesMap` 中。其中,`#getMapKey(ResultMap resultMap, String columnPrefix)` 方法,获得缓存的 KEY 。代码如下:
```
// ResultSetWrapper.java
private String getMapKey(ResultMap resultMap, String columnPrefix) {
return resultMap.getId() + ":" + columnPrefix;
}
```
下面,我们看个类似的,会调用该方法的方法:
- `#getMappedColumnNames(ResultMap resultMap, String columnPrefix)` 方法,获得**有** mapped 的字段的名字的数组。代码如下:
```
// ResultSetWrapper.java
public List<String> getMappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
// 获得对应的 mapped 数组
List<String> mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
if (mappedColumnNames == null) {
// 初始化
loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
// 重新获得对应的 mapped 数组
mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
}
return mappedColumnNames;
}
```
- `#getUnmappedColumnNames(ResultMap resultMap, String columnPrefix)` 方法,获得**无** mapped 的字段的名字的数组。代码如下:
```
// ResultSetWrapper.java
public List<String> getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
// 获得对应的 unMapped 数组
List<String> unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
if (unMappedColumnNames == null) {
// 初始化
loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
// 重新获得对应的 unMapped 数组
unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
}
return unMappedColumnNames;
}
```
😈 具体这两个方法什么用途呢?待到我们在 DefaultResultSetHandler 类里来看。
# 3. ResultSetHandler
`org.apache.ibatis.executor.resultset.ResultSetHandler` `java.sql.ResultSet` 处理器接口。代码如下:
```
// ResultSetHandler.java
public interface ResultSetHandler {
/**
* 处理 {@link java.sql.ResultSet} 成映射的对应的结果
*
* @param stmt Statement 对象
* @param <E> 泛型
* @return 结果数组
*/
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
/**
* 处理 {@link java.sql.ResultSet} 成 Cursor 对象
*
* @param stmt Statement 对象
* @param <E> 泛型
* @return Cursor 对象
*/
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
// 暂时忽略,和存储过程相关
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
```
## 3.1 DefaultResultSetHandler
> 老艿艿保持冷静DefaultResultSetHandler 有小 1000 行的代码。
`org.apache.ibatis.executor.resultset.DefaultResultSetHandler` ,实现 ResultSetHandler 接口,默认的 ResultSetHandler 实现类。
### 3.1.1 构造方法
```
// DefaultResultSetHandler.java
private static final Object DEFERED = new Object();
private final Executor executor;
private final Configuration configuration;
private final MappedStatement mappedStatement;
private final RowBounds rowBounds;
private final ParameterHandler parameterHandler;
/**
* 用户指定的用于处理结果的处理器。
*
* 一般情况下,不设置
*/
private final ResultHandler<?> resultHandler;
private final BoundSql boundSql;
private final TypeHandlerRegistry typeHandlerRegistry;
private final ObjectFactory objectFactory;
private final ReflectorFactory reflectorFactory;
// nested resultmaps
private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
private final Map<String, Object> ancestorObjects = new HashMap<>();
private Object previousRowValue;
// multiple resultsets
// 存储过程相关的多 ResultSet 涉及的属性,可以暂时忽略
private final Map<String, ResultMapping> nextResultMaps = new HashMap<>();
private final Map<CacheKey, List<PendingRelation>> pendingRelations = new HashMap<>();
// Cached Automappings
/**
* 自动映射的缓存
*
* KEY{@link ResultMap#getId()} + ":" + columnPrefix
*
* @see #createRowKeyForUnmappedProperties(ResultMap, ResultSetWrapper, CacheKey, String)
*/
private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();
// temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
/**
* 是否使用构造方法创建该结果对象
*/
private boolean useConstructorMappings;
public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler<?> resultHandler, BoundSql boundSql, RowBounds rowBounds) {
this.executor = executor;
this.configuration = mappedStatement.getConfiguration();
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.parameterHandler = parameterHandler;
this.boundSql = boundSql;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
this.reflectorFactory = configuration.getReflectorFactory();
this.resultHandler = resultHandler;
}
```
- 属性比较多,我们看重点的几个。
- `resultHandler` 属性ResultHandler 对象。用户指定的用于处理结果的处理器,一般情况下,不设置。详细解析,见 [「5. ResultHandler」](https://svip.iocoder.cn/MyBatis/executor-4/#) 和 [「3.1.2.3.3 storeObject」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
- `autoMappingsCache` 属性自动映射的缓存。其中KEY 为 `{@link ResultMap#getId()} + ":" + columnPrefix` 。详细解析,见 [「3.1.2.3.2.4 applyAutomaticMappings」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
### 3.1.2 handleResultSets
`#handleResultSets(Statement stmt)` 方法,处理 `java.sql.ResultSet` 结果集,转换成映射的对应结果。代码如下:
```
// DefaultResultSetHandler.java
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// <1> 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象。而实际上,每个 Object 是 List<Object> 对象。
// 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet 也就是说multipleResults 最多就一个元素。
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// <2> 获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// <3> 获得 ResultMap 数组
// 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet 也就是说resultMaps 就一个元素。
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount); // <3.1> 校验
while (rsw != null && resultMapCount > resultSetCount) {
// <4.1> 获得 ResultMap 对象
ResultMap resultMap = resultMaps.get(resultSetCount);
// <4.2> 处理 ResultSet ,将结果添加到 multipleResults 中
handleResultSet(rsw, resultMap, multipleResults, null);
// <4.3> 获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象
rsw = getNextResultSet(stmt);
// <4.4> 清理
cleanUpAfterHandlingResultSet();
// resultSetCount ++
resultSetCount++;
}
// <5> 因为 `mappedStatement.resultSets` 只在存储过程中使用,本系列暂时不考虑,忽略即可
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++;
}
}
// <6> 如果是 multipleResults 单元素,则取首元素返回
return collapseSingleResultList(multipleResults);
}
```
- 这个方法,不仅仅支持处理 Statement 和 PreparedStatement 返回的结果集,也支持存储过程的 CallableStatement 返回的结果集。而 CallableStatement 是支持返回多结果集的,这个大家要注意。😈 当然,还是老样子,本文不分析仅涉及存储过程的相关代码。哈哈哈。
- `<1>` 处,多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象。而实际上,每个 Object 是 List 对象。在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,`multipleResults` **最多就一个元素**。
- `<2>` 处,调用 `#getFirstResultSet(Statement stmt)` 方法,获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象。代码如下:
```
// DefaultResultSetHandler.java
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
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;
}
}
}
// 将 ResultSet 对象,封装成 ResultSetWrapper 对象
return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}
```
- `<3>` 处,调用 `MappedStatement#getResultMaps()` 方法,获得 ResultMap 数组。在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,`resultMaps` **就一个元素**。
- `<3.1>` 处,调用 `#validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount)` 方法,校验至少有一个 ResultMap 对象。代码如下:
```
// DefaultResultSetHandler.java
private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) {
if (rsw != null && resultMapCount < 1) {
throw new ExecutorException("A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId()
+ "'. It's likely that neither a Result Type nor a Result Map was specified.");
}
}
```
- 不符合,则抛出 ExecutorException 异常。
- `<4.1>` 处,获得 ResultMap 对象。
- `<4.2>` 处,调用 `#handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping)` 方法,处理 ResultSet ,将结果添加到 `multipleResults` 中。详细解析,见 [「3.1.2.1 handleResultSet」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
- `<4.3>` 处,调用 `#getNextResultSet(Statement stmt)` 方法,获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象。😈 只有存储过程才有多 ResultSet 对象,所以可以忽略。也就是说,实际上,这个 `while` 循环对我们来说,就不需要啦。
- `<4.4>` 处,调用 `#cleanUpAfterHandlingResultSet()` 方法,执行清理。代码如下:
```
// DefaultResultSetHandler.java
private void cleanUpAfterHandlingResultSet() {
nestedResultObjects.clear();
}
```
- `<5>` 处,因为 `mappedStatement.resultSets` 只在存储过程中使用,本系列暂时不考虑,忽略即可。
- `<6>` 处,调用 `#collapseSingleResultList(List<Object> multipleResults)` 方法,如果是 `multipleResults` 单元素,则取首元素返回。代码如下:
```
// DefaultResultSetHandler.java
private List<Object> collapseSingleResultList(List<Object> multipleResults) {
return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
}
```
- 对于非存储过程的结果处理,都能符合 `multipleResults.size()` 。
#### 3.1.2.1 handleResultSet
`#handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping)` 方法,处理 ResultSet ,将结果添加到 `multipleResults` 中。代码如下:
```
// DefaultResultSetHandler.java
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
// <1> 暂时忽略因为只有存储过程的情况调用该方法parentMapping 为非空
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
// <2> 如果没有自定义的 resultHandler ,则创建默认的 DefaultResultHandler 对象
if (resultHandler == null) {
// <2> 创建 DefaultResultHandler 对象
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
// <3> 处理 ResultSet 返回的每一行 Row
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
// <4> 添加 defaultResultHandler 的处理的结果,到 multipleResults 中
multipleResults.add(defaultResultHandler.getResultList());
} else {
// <3> 处理 ResultSet 返回的每一行 Row
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
// 关闭 ResultSet 对象
closeResultSet(rsw.getResultSet());
}
}
```
- `<1>` 处,暂时忽略,因为只有存储过程的情况,调用该方法,`parentMapping` 为非空。
- `<2>` 处,如果没有自定义的 `resultHandler` ,则创建默认的 DefaultResultHandler 对象。
- `<3>` 处,调用 `#handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)` 方法,处理 ResultSet 返回的每一行 Row 。详细解析,见 [「3.1.2.2 handleRowValues」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
- 【特殊】`<4>` 处,使用**默认**的 DefaultResultHandler 对象,最终会将 `defaultResultHandler` 的处理的结果,到 `multipleResults` 中。而使用**自定义**的 `resultHandler` ,不会添加到 `multipleResults` 中。当然,因为自定义的 `resultHandler` 对象,是作为一个对象传入,所以在其内部,还是可以存储结果的。例如:
```
@Select("select * from users")
@ResultType(User.class)
void getAllUsers(UserResultHandler resultHandler);
```
- 感兴趣的胖友,可以看看 [《mybatis ResultHandler 示例》](http://outofmemory.cn/code-snippet/13271/mybatis-complex-bean-property-handle-with-custom-ResultHandler) 。
- `<5>` 处,调用 `#closeResultSet(ResultSet rs)` 方法关闭 ResultSet 对象。代码如下:
```
// DefaultResultSetHandler.java
private void closeResultSet(ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
// ignore
}
}
```
#### 3.1.2.2 handleRowValues
`#handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)` 方法,处理 ResultSet 返回的每一行 Row 。代码如下:
```
// DefaultResultSetHandler.java
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// <1> 处理嵌套映射的情况
if (resultMap.hasNestedResultMaps()) {
// 校验不要使用 RowBounds
ensureNoRowBounds();
// 校验不要使用自定义的 resultHandler
checkResultHandler();
// 处理嵌套映射的结果
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
// <2> 处理简单映射的情况
} else {
// <2.1> 处理简单映射的结果
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
```
- 分成**嵌套映射**和**简单映射**的两种情况。
- ```
<2>
```
处理嵌套映射的情况:
- `<2.1>` 处,调用 `#handleRowValuesForSimpleResultMap(...)` 方法,处理简单映射的结果。详细解析,见 [「3.1.2.3 handleRowValuesForSimpleResultMap 简单映射」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
- `<1>` 处理嵌套映射的情况:
- `<1.1>` 处,调用 `#ensureNoRowBounds()` 方法,校验不要使用 RowBounds 。代码如下:
```
// DefaultResultSetHandler.java
private void ensureNoRowBounds() {
// configuration.isSafeRowBoundsEnabled() 默认为 false
if (configuration.isSafeRowBoundsEnabled() && rowBounds != null && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) {
throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. "
+ "Use safeRowBoundsEnabled=false setting to bypass this check.");
}
}
```
- 简单看看即可。
- `<1.2>` 处,调用 `#checkResultHandler()` 方法,校验不要使用自定义的 `resultHandler` 。代码如下:
```
// DefaultResultSetHandler.java
protected void checkResultHandler() {
// configuration.isSafeResultHandlerEnabled() 默认为 false
if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) {
throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. "
+ "Use safeResultHandlerEnabled=false setting to bypass this check "
+ "or ensure your statement returns ordered data and set resultOrdered=true on it.");
}
}
```
- 简单看看即可。
- `<1.3>` 处,调用 `#handleRowValuesForSimpleResultMap(...)` 方法,处理嵌套映射的结果。详细解析,见 [「3.1.2.3 handleRowValuesForNestedResultMap 嵌套映射」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
#### 3.1.2.3 handleRowValuesForSimpleResultMap 简单映射
`#handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)` 方法,处理简单映射的结果。代码如下:
```
// DefaultResultSetHandler.java
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// <1> 创建 DefaultResultContext 对象
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
// <2> 获得 ResultSet 对象,并跳到 rowBounds 指定的开始位置
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
// <3> 循环
while (shouldProcessMoreRows(resultContext, rowBounds) // 是否继续处理 ResultSet
&& !resultSet.isClosed() // ResultSet 是否已经关闭
&& resultSet.next()) { // ResultSet 是否还有下一条
// <4> 根据该行记录以及 ResultMap.discriminator ,决定映射使用的 ResultMap 对象
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// <5> 根据最终确定的 ResultMap 对 ResultSet 中的该行记录进行映射,得到映射后的结果对象
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// <6> 将映射创建的结果对象添加到 ResultHandler.resultList 中保存
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
```
- `<1>` 处,创建 DefaultResultContext 对象。详细解析,胖友**先**跳到 [「4. ResultContext」](https://svip.iocoder.cn/MyBatis/executor-4/#) 中,看完就回来。
- `<2>` 处,获得 ResultSet 对象,并调用 `#skipRows(ResultSet rs, RowBounds rowBounds)` 方法,跳到 `rowBounds` 指定的开始位置。代码如下:
```
// DefaultResultSetHandler.java
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
// 直接跳转到指定开始的位置
if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
rs.absolute(rowBounds.getOffset());
}
} else {
// 循环,不断跳到开始的位置
for (int i = 0; i < rowBounds.getOffset(); i++) {
if (!rs.next()) {
break;
}
}
}
}
```
- 关于 `org.apache.ibatis.session.RowBounds` 类,胖友可以看看 [《Mybatis3.3.x技术内幕十三Mybatis之RowBounds分页原理》](https://my.oschina.net/zudajun/blog/671446) ,解释的非常不错。
- `<3>` 处,循环,满足如下三个条件。其中 `#shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds)` 方法,是否继续处理 ResultSet 。代码如下:
```
// DefaultResultSetHandler.java
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}
```
- `<4>` 处,调用 `#resolveDiscriminatedResultMap(...)` 方法,根据该行记录以及 `ResultMap.discriminator` ,决定映射使用的 ResultMap 对象。详细解析,等下看 [「3.1.2.3.1 resolveDiscriminatedResultMap」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
- `<5>` 处,调用 `#getRowValue(...)` 方法,根据最终确定的 ResultMap 对 ResultSet 中的该行记录进行映射,得到映射后的结果对象。详细解析,等下看 [「3.1.2.3.2 getRowValue」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
- `<6>` 处,调用 `#storeObject(...)` 方法,将映射创建的结果对象添加到 `ResultHandler.resultList` 中保存。详细解析,等下看 [「3.1.2.3.3 storeObject」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
##### 3.1.2.3.1 resolveDiscriminatedResultMap
`#resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)` 方法,根据该行记录以及 `ResultMap.discriminator` ,决定映射使用的 ResultMap 对象。代码如下:
```
// DefaultResultSetHandler.java
public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
// 记录已经处理过的 Discriminator 对应的 ResultMap 的编号
Set<String> pastDiscriminators = new HashSet<>();
// 如果存在 Discriminator 对象,则基于其获得 ResultMap 对象
Discriminator discriminator = resultMap.getDiscriminator();
while (discriminator != null) { // 因为 Discriminator 可以嵌套 Discriminator ,所以是一个递归的过程
// 获得 Discriminator 的指定字段,在 ResultSet 中该字段的值
final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
// 从 Discriminator 获取该值对应的 ResultMap 的编号
final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
// 如果存在,则使用该 ResultMap 对象
if (configuration.hasResultMap(discriminatedMapId)) {
// 获得该 ResultMap 对象
resultMap = configuration.getResultMap(discriminatedMapId);
// 判断,如果出现“重复”的情况,结束循环
Discriminator lastDiscriminator = discriminator;
discriminator = resultMap.getDiscriminator();
if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
break;
}
// 如果不存在,直接结束循环
} else {
break;
}
}
return resultMap;
}
```
- 对于大多数情况下,大家不太会使用 Discriminator 的功能,此处就直接返回 `resultMap` ,不会执行这个很复杂的逻辑。😈 所以,如果看不太懂的胖友,可以略过这个方法,问题也不大。
- 代码比较繁杂,胖友跟着注释看看,甚至可以调试下。其中,`<1>` 处,调用 `#getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix)` 方法,获得 Discriminator 的指定字段,在 ResultSet 中该字段的值。代码如下:
```
// DefaultResultSetHandler.java
/**
* 获得 ResultSet 的指定字段的值
*
* @param rs ResultSet 对象
* @param discriminator Discriminator 对象
* @param columnPrefix 字段名的前缀
* @return 指定字段的值
*/
private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException {
final ResultMapping resultMapping = discriminator.getResultMapping();
final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
// 获得 ResultSet 的指定字段的值
return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
}
/**
* 拼接指定字段的前缀
*
* @param columnName 字段的名字
* @param prefix 前缀
* @return prefix + columnName
*/
private String prependPrefix(String columnName, String prefix) {
if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
return columnName;
}
return prefix + columnName;
}
```
##### 3.1.2.3.2 getRowValue
`#getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)` 方法,根据最终确定的 ResultMap 对 ResultSet 中的该行记录进行映射,得到映射后的结果对象。代码如下:
```
// DefaultResultSetHandler.java
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
// <1> 创建 ResultLoaderMap 对象
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// <2> 创建映射后的结果对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
// <3> 如果 hasTypeHandlerForResultObject(rsw, resultMap.getType()) 返回 true ,意味着 rowValue 是基本类型,无需执行下列逻辑。
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// <4> 创建 MetaObject 对象,用于访问 rowValue 对象
final MetaObject metaObject = configuration.newMetaObject(rowValue);
// <5> foundValues 代表,是否成功映射任一属性。若成功,则为 true ,若失败,则为 false
boolean foundValues = this.useConstructorMappings;
/// <6.1> 判断是否开启自动映射功能
if (shouldApplyAutomaticMappings(resultMap, false)) {
// <6.2> 自动映射未明确的列
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// <7> 映射 ResultMap 中明确映射的列
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
// <8> ↑↑↑ 至此,当前 ResultSet 的该行记录的数据,已经完全映射到结果对象 rowValue 的对应属性种
foundValues = lazyLoader.size() > 0 || foundValues;
// <9> 如果没有成功映射任意属性,则置空 rowValue 对象。
// 当然,如果开启 `configuration.returnInstanceForEmptyRow` 属性,则不置空。默认情况下,该值为 false
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
```
- `<1>` 处,创建 ResultLoaderMap 对象。延迟加载相关。
- `<2>` 处,调用 `#createResultObject(...)` 方法,创建映射后的结果对象。详细解析,见 [「3.1.2.3.2.1 createResultObject」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。😈 mmp ,这个逻辑的嵌套,真的太深太深了。
- `<3>` 处,调用 `#hasTypeHandlerForResultObject(rsw, resultMap.getType())` 方法,返回 `true` ,意味着 `rowValue` 是基本类型,无需执行下列逻辑。代码如下:
```
// DefaultResultSetHandler.java
// 判断是否结果对象是否有 TypeHandler 对象
private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
// 如果返回的字段只有一个,则直接判断该字段是否有 TypeHandler 对象
if (rsw.getColumnNames().size() == 1) {
return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
}
// 判断 resultType 是否有对应的 TypeHandler 对象
return typeHandlerRegistry.hasTypeHandler(resultType);
}
```
- 有点绕,胖友可以调试下 `BindingTest#shouldInsertAuthorWithSelectKeyAndDynamicParams()` 方法。
- 再例如,`<select resultType="Integer" />` 的情况。
- `<4>` 处,创建 MetaObject 对象,用于访问 `rowValue` 对象。
- `<5>` 处,`foundValues` 代表,是否成功映射任一属性。若成功,则为 `true` ,若失败,则为 `false` 。另外,此处使用 `useConstructorMappings` 作为 `foundValues` 的初始值,原因是,使用了构造方法创建该结果对象,意味着一定找到了任一属性。
- `<6.1>` 处,调用 `#shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested)` 方法,判断是否使用自动映射的功能。代码如下:
```
// DefaultResultSetHandler.java
private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
// 判断是否开启自动映射功能
if (resultMap.getAutoMapping() != null) {
return resultMap.getAutoMapping();
} else {
// 内嵌查询或嵌套映射时
if (isNested) {
return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior(); // 需要 FULL
// 普通映射
} else {
return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior(); // 需要 PARTIAL 或 FULL
}
}
}
```
- `org.apache.ibatis.session.AutoMappingBehavior` ,自动映射行为的枚举。代码如下:
```
// AutoMappingBehavior.java
/**
* Specifies if and how MyBatis should automatically map columns to fields/properties.
*
* 自动映射行为的枚举
*
* @author Eduardo Macarron
*/
public enum AutoMappingBehavior {
/**
* Disables auto-mapping.
*
* 禁用自动映射的功能
*/
NONE,
/**
* Will only auto-map results with no nested result mappings defined inside.
*
* 开启部分映射的功能
*/
PARTIAL,
/**
* Will auto-map result mappings of any complexity (containing nested or otherwise).
*
* 开启全部映射的功能
*/
FULL
}
```
- x
- `Configuration.autoMappingBehavior` 属性,默认为 `AutoMappingBehavior.PARTIAL` 。
- `<6.2>` 处,调用 `#applyAutomaticMappings(...)` 方法,自动映射未明确的列。代码有点长,所以,详细解析,见 [「3.1.2.3.2.3 applyAutomaticMappings」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
- `<7>` 处,调用 `#applyPropertyMappings(...)` 方法,映射 ResultMap 中明确映射的列。代码有点长,所以,详细解析,见 [「3.1.2.3.2.4 applyPropertyMappings」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
- `<8>` 处,↑↑↑ 至此,当前 ResultSet 的该行记录的数据,已经完全映射到结果对象 `rowValue` 的对应属性中。😈 整个过程,非常非常非常长,胖友耐心理解和调试下。
- `<9>` 处,如果没有成功映射任意属性,则置空 rowValue 对象。当然,如果开启 `configuration.returnInstanceForEmptyRow` 属性,则不置空。默认情况下,该值为 `false` 。
###### 3.1.2.3.2.1 createResultObject
`#createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix)` 方法,创建映射后的结果对象。代码如下:
```
// DefaultResultSetHandler.java
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
// <1> useConstructorMappings ,表示是否使用构造方法创建该结果对象。此处将其重置
this.useConstructorMappings = false; // reset previous mapping result
final List<Class<?>> constructorArgTypes = new ArrayList<>(); // 记录使用的构造方法的参数类型的数组
final List<Object> constructorArgs = new ArrayList<>(); // 记录使用的构造方法的参数值的数组
// <2> 创建映射后的结果对象
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// <3> 如果有内嵌的查询,并且开启延迟加载,则创建结果对象的代理对象
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
// <4> 判断是否使用构造方法创建该结果对象
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
```
- `<1>` 处,`useConstructorMappings` ,表示是否使用构造方法创建该结果对象。而此处,将其重置为 `false` 。
- `<2>` 处,调用 `#createResultObject(...)` 方法,创建映射后的结果对象。详细解析,往下看。😈 再次 mmp ,调用链太长了。
- `<3>` 处,如果有内嵌的查询,并且开启延迟加载,则调用 `ProxyFactory#createProxy(...)` 方法,创建结果对象的代理对象。详细解析,见 [《精尽 MyBatis 源码分析 —— SQL 执行(五)之延迟加载》](http://svip.iocoder.cn/MyBatis/executor-5) 。
- `<4>` 处,判断是否使用构造方法创建该结果对象,并设置到 `useConstructorMappings` 中。
------
`#createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)` 方法,创建映射后的结果对象。代码如下:
```
// DefaultResultSetHandler.java
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
throws SQLException {
final Class<?> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
// 下面,分成四种创建结果对象的情况
// <1> 情况一,如果有对应的 TypeHandler 对象,则意味着是基本类型,直接创建对结果应对象
if (hasTypeHandlerForResultObject(rsw, resultType)) {
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
// 情况二,如果 ResultMap 中,如果定义了 `<constructor />` 节点,则通过反射调用该构造方法,创建对应结果对象
} else if (!constructorMappings.isEmpty()) {
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
// 情况三,如果有默认的无参的构造方法,则使用该构造方法,创建对应结果对象
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
return objectFactory.create(resultType);
// 情况四,通过自动映射的方式查找合适的构造方法,后使用该构造方法,创建对应结果对象
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
}
// 不支持,抛出 ExecutorException 异常
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
```
- 分成四种创建结果对象的情况。
- `<1>` 处,情况一,如果有对应的 TypeHandler 对象,则意味着是基本类型,直接创建对结果应对象。调用 `#createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)` 方法,代码如下:
```
// DefaultResultSetHandler.java
private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final Class<?> resultType = resultMap.getType();
// 获得字段名
final String columnName;
if (!resultMap.getResultMappings().isEmpty()) {
final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
final ResultMapping mapping = resultMappingList.get(0);
columnName = prependPrefix(mapping.getColumn(), columnPrefix);
} else {
columnName = rsw.getColumnNames().get(0);
}
// 获得 TypeHandler 对象
final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
// 获得 ResultSet 的指定字段的值
return typeHandler.getResult(rsw.getResultSet(), columnName);
}
```
- `<2>` 处,情况二,如果 ResultMap 中,如果定义了 `<constructor />` 节点,则通过反射调用该构造方法,创建对应结果对象。调用 `#createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)` 方法,代码如下:
```
// DefaultResultSetHandler.java
Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings,
List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) {
// 获得到任一的属性值。即,只要一个结果对象,有一个属性非空,就会设置为 true
boolean foundValues = false;
for (ResultMapping constructorMapping : constructorMappings) {
// 获得参数类型
final Class<?> parameterType = constructorMapping.getJavaType();
// 获得数据库的字段名
final String column = constructorMapping.getColumn();
// 获得属性值
final Object value;
try {
// 如果是内嵌的查询,则获得内嵌的值
if (constructorMapping.getNestedQueryId() != null) {
value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
// 如果是内嵌的 resultMap ,则递归 getRowValue 方法,获得对应的属性值
} else if (constructorMapping.getNestedResultMapId() != null) {
final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
value = getRowValue(rsw, resultMap, constructorMapping.getColumnPrefix());
// 最常用的情况,直接使用 TypeHandler 获取当前 ResultSet 的当前行的指定字段的值
} else {
final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
}
} catch (ResultMapException | SQLException e) {
throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
}
// 添加到 constructorArgTypes 和 constructorArgs 中
constructorArgTypes.add(parameterType);
constructorArgs.add(value);
// 判断是否获得到属性值
foundValues = value != null || foundValues;
}
// 查找 constructorArgTypes 对应的构造方法
// 查找到后,传入 constructorArgs 作为参数,创建结果对象
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
```
- 代码比较简单,胖友看下注释即可。
- 当然,里面的 `#getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)` 方法的逻辑,还是略微比较复杂的。所以,我们在讲完情况三、情况四,我们再来看看它的实现。😈 写到这里,艿艿的心里无比苦闷。详细解析,见 [「3.1.2.3.2.2 getNestedQueryConstructorValue」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
- `<3>` 处,情况三,如果有默认的无参的构造方法,则使用该构造方法,创建对应结果对象。
- `<4>` 处,情况四,通过自动映射的方式查找合适的构造方法,后使用该构造方法,创建对应结果对象。调用 `#createByConstructorSignature(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)` 方法,代码如下:
```
// DefaultResultSetHandler.java
private Object createByConstructorSignature(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
String columnPrefix) throws SQLException {
// <1> 获得所有构造方法
final Constructor<?>[] constructors = resultType.getDeclaredConstructors();
// <2> 获得默认构造方法
final Constructor<?> defaultConstructor = findDefaultConstructor(constructors);
// <3> 如果有默认构造方法,使用该构造方法,创建结果对象
if (defaultConstructor != null) {
return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix, defaultConstructor);
} else {
// <4> 遍历所有构造方法,查找符合的构造方法,创建结果对象
for (Constructor<?> constructor : constructors) {
if (allowedConstructorUsingTypeHandlers(constructor, rsw.getJdbcTypes())) {
return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix, constructor);
}
}
}
throw new ExecutorException("No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames());
}
```
- `<1>` 处,获得所有构造方法。
- `<2>` 处,调用 `#findDefaultConstructor(final Constructor<?>[] constructors)` 方法,获得默认构造方法。代码如下:
```
// DefaultResultSetHandler.java
private Constructor<?> findDefaultConstructor(final Constructor<?>[] constructors) {
// 构造方法只有一个,直接返回
if (constructors.length == 1) return constructors[0];
// 获得使用 @AutomapConstructor 注解的构造方法
for (final Constructor<?> constructor : constructors) {
if (constructor.isAnnotationPresent(AutomapConstructor.class)) {
return constructor;
}
}
return null;
}
```
- 两种情况,比较简单。
- `<3>` 处,如果有默认构造方法,调用 `#createUsingConstructor(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix, Constructor<?> constructor)` 方法,使用该构造方法,创建结果对象。代码如下:
```
// DefaultResultSetHandler.java
private Object createUsingConstructor(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix, Constructor<?> constructor) throws SQLException {
boolean foundValues = false;
for (int i = 0; i < constructor.getParameterTypes().length; i++) {
// 获得参数类型
Class<?> parameterType = constructor.getParameterTypes()[i];
// 获得数据库的字段名
String columnName = rsw.getColumnNames().get(i);
// 获得 TypeHandler 对象
TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
// 获取当前 ResultSet 的当前行的指定字段的值
Object value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(columnName, columnPrefix));
// 添加到 constructorArgTypes 和 constructorArgs 中
constructorArgTypes.add(parameterType);
constructorArgs.add(value);
// 判断是否获得到属性值
foundValues = value != null || foundValues;
}
// 查找 constructorArgTypes 对应的构造方法
// 查找到后,传入 constructorArgs 作为参数,创建结果对象
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
```
- 从代码实现上,和 `#createParameterizedResultObject(...)` 方法,类似。
- `<4>` 处,遍历所有构造方法,调用 `#allowedConstructorUsingTypeHandlers(final Constructor<?> constructor, final List<JdbcType> jdbcTypes)` 方法,查找符合的构造方法,后创建结果对象。代码如下:
```
// DefaultResultSetHandler.java
private boolean allowedConstructorUsingTypeHandlers(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
final Class<?>[] parameterTypes = constructor.getParameterTypes();
// 结果集的返回字段的数量,要和构造方法的参数数量,一致
if (parameterTypes.length != jdbcTypes.size()) return false;
// 每个构造方法的参数,和对应的返回字段,都要有对应的 TypeHandler 对象
for (int i = 0; i < parameterTypes.length; i++) {
if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
return false;
}
}
// 返回匹配
return true;
}
```
- 基于结果集的返回字段和构造方法的参数做比较。
###### 3.1.2.3.2.2 getNestedQueryConstructorValue 嵌套查询
> 老艿艿:冲鸭!!!太冗长了!!!各种各种各种!!!!情况!!!!!
`#getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)` 方法,获得嵌套查询的值。代码如下:
```
// DefaultResultSetHandler.java
private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix) throws SQLException {
// <1> 获得内嵌查询的编号
final String nestedQueryId = constructorMapping.getNestedQueryId();
// <1> 获得内嵌查询的 MappedStatement 对象
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
// <1> 获得内嵌查询的参数类型
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
// <2> 获得内嵌查询的参数对象
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, nestedQueryParameterType, columnPrefix);
Object value = null;
// <3> 执行查询
if (nestedQueryParameterObject != null) {
// <3.1> 获得 BoundSql 对象
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
// <3.2> 获得 CacheKey 对象
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
final Class<?> targetType = constructorMapping.getJavaType();
// <3.3> 创建 ResultLoader 对象
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
// <3.3> 加载结果
value = resultLoader.loadResult();
}
return value;
}
```
- 关于这个方法,胖友可以调试 `BaseExecutorTest#shouldFetchOneOrphanedPostWithNoBlog()` 这个单元测试方法。
- `<1>` 处,获得内嵌查询的**编号**、**MappedStatement 对象**、**参数类型**。
- `<2>` 处,调用 `#prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix)` 方法,获得内嵌查询的**参数对象**。代码如下:
```
// DefaultResultSetHandler.java
// 获得内嵌查询的参数类型
private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
if (resultMapping.isCompositeResult()) { // ② 组合
return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
} else { // ① 普通
return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
}
}
// ① 获得普通类型的内嵌查询的参数对象
private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
// 获得 TypeHandler 对象
final TypeHandler<?> typeHandler;
if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
} else {
typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
}
// 获得指定字段的值
return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
}
// ② 获得组合类型的内嵌查询的参数对象
private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
// 创建参数对象
final Object parameterObject = instantiateParameterObject(parameterType);
// 创建参数对象的 MetaObject 对象,可对其进行访问
final MetaObject metaObject = configuration.newMetaObject(parameterObject);
boolean foundValues = false;
// 遍历组合的所有字段
for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
// 获得属性类型
final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
// 获得对应的 TypeHandler 对象
final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
// 获得指定字段的值
final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
// issue #353 & #560 do not execute nested query if key is null
// 设置到 parameterObject 中,通过 metaObject
if (propValue != null) {
metaObject.setValue(innerResultMapping.getProperty(), propValue);
foundValues = true; // 标记 parameterObject 非空对象
}
}
// 返回参数对象
return foundValues ? parameterObject : null;
}
// ② 创建参数对象
private Object instantiateParameterObject(Class<?> parameterType) {
if (parameterType == null) {
return new HashMap<>();
} else if (ParamMap.class.equals(parameterType)) {
return new HashMap<>(); // issue #649
} else {
return objectFactory.create(parameterType);
}
}
```
- 虽然代码比较长,但是非常简单。注意下,艿艿添加了 `` 和 `` 两个序号,分别对应两种情况。
- ```
<3>
```
处,整体是,执行查询,获得值。
- `<3.1>` 处,调用 `MappedStatement#getBoundSql(Object parameterObject)` 方法,获得 BoundSql 对象。
- `<3.2>` 处,创建 CacheKey 对象。
- `<3.3>` 处,创建 ResultLoader 对象,并调用 `ResultLoader#loadResult()` 方法,加载结果。详细解析,见 [《精尽 MyBatis 源码分析 —— SQL 执行(五)之延迟加载》](http://svip.iocoder.cn/MyBatis/executor-5) 。
###### 3.1.2.3.2.3 applyAutomaticMappings
`#createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)` 方法,创建映射后的结果对象。代码如下:
```
// DefaultResultSetHandler.java
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
// <1> 获得 UnMappedColumnAutoMapping 数组
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
// <2> 遍历 UnMappedColumnAutoMapping 数组
for (UnMappedColumnAutoMapping mapping : autoMapping) {
// 获得指定字段的值
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
// 若非空,标记 foundValues 有值
if (value != null) {
foundValues = true;
}
// 设置到 parameterObject 中,通过 metaObject
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
```
- `<1>` 处,调用 `#createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix)` 方法,获得 UnMappedColumnAutoMapping 数组。代码如下:
```
// DefaultResultSetHandler.java
private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
// 生成 autoMappingsCache 的 KEY
final String mapKey = resultMap.getId() + ":" + columnPrefix;
// 从缓存 autoMappingsCache 中,获得 UnMappedColumnAutoMapping 数组
List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
// 如果获取不到,则进行初始化
if (autoMapping == null) {
autoMapping = new ArrayList<>();
// 获得未 mapped 的字段的名字的数组
final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
// 遍历 unmappedColumnNames 数组
for (String columnName : unmappedColumnNames) {
// 获得属性名
String propertyName = columnName;
if (columnPrefix != null && !columnPrefix.isEmpty()) {
// When columnPrefix is specified,
// ignore columns without the prefix.
if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
propertyName = columnName.substring(columnPrefix.length());
} else {
continue;
}
}
// 从结果对象的 metaObject 中,获得对应的属性名
final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
// 获得到属性名,并且可以进行设置
if (property != null && metaObject.hasSetter(property)) {
// 排除已映射的属性
if (resultMap.getMappedProperties().contains(property)) {
continue;
}
// 获得属性的类型
final Class<?> propertyType = metaObject.getSetterType(property);
// 判断是否有对应的 TypeHandler 对象。如果有,则创建 UnMappedColumnAutoMapping 对象,并添加到 autoMapping 中
if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
// 如果没有,则执行 AutoMappingUnknownColumnBehavior 对应的逻辑
} else {
configuration.getAutoMappingUnknownColumnBehavior()
.doAction(mappedStatement, columnName, property, propertyType);
}
// 如果没有属性,或者无法设置,则则执行 AutoMappingUnknownColumnBehavior 对应的逻辑
} else {
configuration.getAutoMappingUnknownColumnBehavior()
.doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
}
}
// 添加到缓存中
autoMappingsCache.put(mapKey, autoMapping);
}
return autoMapping;
}
```
- 虽然代码比较长,但是逻辑很简单。遍历未 mapped 的字段的名字的数组,映射每一个字段在**结果对象**的相同名字的属性,最终生成 UnMappedColumnAutoMapping 对象。
- UnMappedColumnAutoMapping ,是 DefaultResultSetHandler 的内部静态类,未 mapped 字段自动映射后的对象。代码如下:
```
// DefaultResultSetHandler.java
private static class UnMappedColumnAutoMapping {
/**
* 字段名
*/
private final String column;
/**
* 属性名
*/
private final String property;
/**
* TypeHandler 处理器
*/
private final TypeHandler<?> typeHandler;
/**
* 是否为基本属性
*/
private final boolean primitive;
public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
this.column = column;
this.property = property;
this.typeHandler = typeHandler;
this.primitive = primitive;
}
}
```
- x
- 当找不到映射的属性时,会调用 `AutoMappingUnknownColumnBehavior#doAction(MappedStatement mappedStatement, String columnName, String propertyName, Class<?> propertyType)` 方法,执行相应的逻辑。比较简单,胖友直接看 [`org.apache.ibatis.session.AutoMappingUnknownColumnBehavior`](https://github.com/YunaiV/mybatis-3/blob/master/src/main/java/org/apache/ibatis/session/AutoMappingUnknownColumnBehavior.java) 即可。
- `Configuration.autoMappingUnknownColumnBehavior` 为 `AutoMappingUnknownColumnBehavior.NONE` ,即**不处理**。
- `<2>` 处,遍历 UnMappedColumnAutoMapping 数组,获得指定字段的值,设置到 `parameterObject` 中,通过 `metaObject` 。
###### 3.1.2.3.2.4 applyPropertyMappings
`#applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)` 方法,映射 ResultMap 中明确映射的列。代码如下:
```
// DefaultResultSetHandler.java
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// 获得 mapped 的字段的名字的数组
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
// 遍历 ResultMapping 数组
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// 获得字段名
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult() // 组合
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) // 属于 mappedColumnNames
|| propertyMapping.getResultSet() != null) { // 存储过程
// <1> 获得指定字段的值
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
// 存储过程相关,忽略
} else if (value == DEFERED) {
foundValues = true;
continue;
}
// 标记获取到任一属性
if (value != null) {
foundValues = true;
}
// 设置到 parameterObject 中,通过 metaObject
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
```
- 虽然代码比较长,但是逻辑很简单。胖友自己瞅瞅。
- 在 `<1>` 处,调用 `#getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)` 方法,获得指定字段的值。代码如下:
```
// DefaultResultSetHandler.java
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// <2> 内嵌查询,获得嵌套查询的值
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
// 存储过程相关,忽略
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERED;
// 普通,直接获得指定字段的值
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
```
- 在 `<2>` 处,我们又碰到了一个内嵌查询,调用 `#getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)` 方法,获得嵌套查询的值。详细解析,见 [「」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
###### 3.1.2.3.2.5 getNestedQueryMappingValue 嵌套查询
`#getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)` 方法,获得嵌套查询的值。代码如下:
```
// DefaultResultSetHandler.java
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// 获得内嵌查询的编号
final String nestedQueryId = propertyMapping.getNestedQueryId();
// 获得属性名
final String property = propertyMapping.getProperty();
// 获得内嵌查询的 MappedStatement 对象
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
// 获得内嵌查询的参数类型
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
// 获得内嵌查询的参数对象
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
Object value = null;
if (nestedQueryParameterObject != null) {
// 获得 BoundSql 对象
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
// 获得 CacheKey 对象
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
final Class<?> targetType = propertyMapping.getJavaType();
// <1> 检查缓存中已存在
if (executor.isCached(nestedQuery, key)) { // 有缓存
// <2.1> 创建 DeferredLoad 对象,并通过该 DeferredLoad 对象从缓存中加载结采对象
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
// <2.2> 返回已定义
value = DEFERED;
// 检查缓存中不存在
} else { // 无缓存
// <3.1> 创建 ResultLoader 对象
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
// <3.2> 如果要求延迟加载,则延迟加载
if (propertyMapping.isLazy()) {
// 如果该属性配置了延迟加载,则将其添加到 `ResultLoader.loaderMap` 中,等待真正使用时再执行嵌套查询并得到结果对象。
lazyLoader.addLoader(property, metaResultObject, resultLoader);
// 返回已定义
value = DEFERED;
// <3.3> 如果不要求延迟加载,则直接执行加载对应的值
} else {
value = resultLoader.loadResult();
}
}
}
return value;
}
```
- 和
「3.1.2.3.2.2 getNestedQueryConstructorValue」
一样,也是
嵌套查询
。所以,从整体代码的实现上,也是非常
类似
的。差别在于:
- `#getNestedQueryConstructorValue(...)` 方法,用于构造方法需要用到的嵌套查询的值,它是**不用**考虑延迟加载的。
- `#getNestedQueryMappingValue(...)` 方法,用于 setting 方法需要用到的嵌套查询的值,它是**需要**考虑延迟加载的。
- `<1>` 处,调用 `Executor#isCached(MappedStatement ms, CacheKey key)` 方法,检查缓存中已存在。下面,我们分成两种情况来解析。
- ========== 有缓存 ==========
- `<2.1>` 处,调用 `Executor#deferLoad(...)` 方法,创建 DeferredLoad 对象,并通过该 DeferredLoad 对象从缓存中加载结采对象。详细解析,见 [《精尽 MyBatis 源码分析 —— SQL 执行(五)之延迟加载》](http://svip.iocoder.cn/MyBatis/executor-5) 。
- `<2.2>` 处,返回已定义 `DEFERED` 。
- ========== 有缓存 ==========
- `<3.1>` 处,创建 ResultLoader 对象。
- ```
<3.2>
```
处,如果要求延迟加载,则延迟加载。
- `<3.2.1>` 处,调用 `ResultLoader#addLoader(...)` 方法,如果该属性配置了延迟加载,则将其添加到 `ResultLoader.loaderMap` 中,等待真正使用时再执行嵌套查询并得到结果对象。详细解析,见 [《精尽 MyBatis 源码分析 —— SQL 执行(五)之延迟加载》](http://svip.iocoder.cn/MyBatis/executor-5) 。
- `<3.3>` 处,如果不要求延迟加载,则调用 `ResultLoader#loadResult()` 方法,直接执行加载对应的值。
##### 3.1.2.3.3 storeObject
`#storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs)` 方法,将映射创建的结果对象添加到 `ResultHandler.resultList` 中保存。代码如下:
```
// DefaultResultSetHandler.java
private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
// 暂时忽略,这个情况,只有存储过程会出现
if (parentMapping != null) {
linkToParents(rs, parentMapping, rowValue);
} else {
callResultHandler(resultHandler, resultContext, rowValue);
}
}
@SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object>*/)
// 调用 ResultHandler ,进行结果的处理
private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
// 设置结果对象到 resultContext 中
resultContext.nextResultObject(rowValue);
// <x> 使用 ResultHandler 处理结果。
// 如果使用 DefaultResultHandler 实现类的情况,会将映射创建的结果对象添加到 ResultHandler.resultList 中保存
((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}
```
- 逻辑比较简单,认真看下注释,特别是 `<x>` 处。
#### 3.1.2.4 handleRowValuesForNestedResultMap 嵌套映射
可能胖友对**嵌套映射**的概念不是很熟悉,胖友可以调试 `AncestorRefTest#testAncestorRef()` 这个单元测试方法。
------
> 老艿艿:本小节,还是建议看 [《MyBatis 技术内幕》](https://item.jd.com/12125531.html) 的 [「3.3.4 嵌套映射」](https://svip.iocoder.cn/MyBatis/executor-4/#) 小节。因为,它提供了比较好的这块逻辑的原理讲解,并且配置了大量的图。
>
> 😈 精力有限,后续补充哈。
> 😜 实际是,因为艿艿比较少用嵌套映射,所以对这块逻辑,不是很感兴趣。
`#handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)` 方法,处理嵌套**映射**的结果。代码如下:
```
// DefaultResultSetHandler.java
```
- TODO 9999 状态不太好,有点写不太明白。不感兴趣的胖友,可以跳过。感兴趣的胖友,可以看看 [《MyBatis 技术内幕》](https://item.jd.com/12125531.html) 的 [「3.3.4 嵌套映射」](https://svip.iocoder.cn/MyBatis/executor-4/#) 小节。
### 3.1.3 handleCursorResultSets
`#handleCursorResultSets(Statement stmt)` 方法,处理 `java.sql.ResultSet` 成 Cursor 对象。代码如下:
```
// DefaultResultSetHandler.java
@Override
public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());
// 获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 游标方式的查询,只允许一个 ResultSet 对象。因此resultMaps 数组的数量,元素只能有一个
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
if (resultMapCount != 1) {
throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps");
}
// 获得 ResultMap 对象,后创建 DefaultCursor 对象
ResultMap resultMap = resultMaps.get(0);
return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
}
```
- 最终,创建成 DefaultCursor 对象。详细解析,见 [「6. DefaultCursor」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
- 可能很多人没用 MyBatis Cursor 功能,所以可以看看 [《Mybatis 3.4.0 Cursor的使用》](https://www.jianshu.com/p/97d96201295b) 。
# 4. ResultContext
> 老艿艿:这个类,大体看看每个方法的用途,结合上文一起理解即可。
`org.apache.ibatis.session.ResultContext` ,结果上下文接口。代码如下:
```
// ResultContext.java
public interface ResultContext<T> {
/**
* @return 当前结果对象
*/
T getResultObject();
/**
* @return 总的结果对象的数量
*/
int getResultCount();
/**
* @return 是否暂停
*/
boolean isStopped();
/**
* 暂停
*/
void stop();
}
```
## 4.1 DefaultResultContext
`org.apache.ibatis.executor.result.DefaultResultContext` ,实现 ResultContext 接口,默认的 ResultContext 的实现类。代码如下:
```
// DefaultResultContext.java
public class DefaultResultContext<T> implements ResultContext<T> {
/**
* 当前结果对象
*/
private T resultObject;
/**
* 总的结果对象的数量
*/
private int resultCount;
/**
* 是否暂停
*/
private boolean stopped;
public DefaultResultContext() {
resultObject = null;
resultCount = 0;
stopped = false; // 默认非暂停
}
@Override
public T getResultObject() {
return resultObject;
}
@Override
public int getResultCount() {
return resultCount;
}
@Override
public boolean isStopped() {
return stopped;
}
/**
* 当前结果对象
*
* @param resultObject 当前结果对象
*/
public void nextResultObject(T resultObject) {
// 数量 + 1
resultCount++;
// 当前结果对象
this.resultObject = resultObject;
}
@Override
public void stop() {
this.stopped = true;
}
}
```
# 5. ResultHandler
`org.apache.ibatis.session.ResultHandler` ,结果处理器接口。代码如下:
```
// ResultHandler.java
public interface ResultHandler<T> {
/**
* 处理当前结果
*
* @param resultContext ResultContext 对象。在其中,可以获得当前结果
*/
void handleResult(ResultContext<? extends T> resultContext);
}
```
## 5.1 DefaultResultHandler
`org.apache.ibatis.executor.result.DefaultResultHandler` ,实现 ResultHandler 接口,默认的 ResultHandler 的实现类。代码如下:
```
// DefaultResultHandler.java
public class DefaultResultHandler implements ResultHandler<Object> {
/**
* 结果数组
*/
private final List<Object> list;
public DefaultResultHandler() {
list = new ArrayList<>();
}
@SuppressWarnings("unchecked")
public DefaultResultHandler(ObjectFactory objectFactory) {
list = objectFactory.create(List.class);
}
@Override
public void handleResult(ResultContext<? extends Object> context) {
// <1> 将当前结果,添加到结果数组中
list.add(context.getResultObject());
}
public List<Object> getResultList() {
return list;
}
}
```
- 核心代码就是 `<1>` 处,将当前结果,添加到结果数组中。
## 5.2 DefaultMapResultHandler
该类在 `session` 包中实现,我们放在**会话模块**的文章中,详细解析。
# 6. Cursor
`org.apache.ibatis.cursor.Cursor` ,继承 Closeable、Iterable 接口,游标接口。代码如下:
```
// Cursor.java
public interface Cursor<T> extends Closeable, Iterable<T> {
/**
* 是否处于打开状态
*
* @return true if the cursor has started to fetch items from database.
*/
boolean isOpen();
/**
* 是否全部消费完成
*
* @return true if the cursor is fully consumed and has returned all elements matching the query.
*/
boolean isConsumed();
/**
* 获得当前索引
*
* Get the current item index. The first item has the index 0.
* @return -1 if the first cursor item has not been retrieved. The index of the current item retrieved.
*/
int getCurrentIndex();
}
```
## 6.1 DefaultCursor
`org.apache.ibatis.cursor.defaults.DefaultCursor` ,实现 Cursor 接口,默认 Cursor 实现类。
### 6.1.1 构造方法
```
// DefaultCursor.java
// ResultSetHandler stuff
private final DefaultResultSetHandler resultSetHandler;
private final ResultMap resultMap;
private final ResultSetWrapper rsw;
private final RowBounds rowBounds;
/**
* ObjectWrapperResultHandler 对象
*/
private final ObjectWrapperResultHandler<T> objectWrapperResultHandler = new ObjectWrapperResultHandler<>();
/**
* CursorIterator 对象,游标迭代器。
*/
private final CursorIterator cursorIterator = new CursorIterator();
/**
* 是否开始迭代
*
* {@link #iterator()}
*/
private boolean iteratorRetrieved;
/**
* 游标状态
*/
private CursorStatus status = CursorStatus.CREATED;
/**
* 已完成映射的行数
*/
private int indexWithRowBound = -1;
public DefaultCursor(DefaultResultSetHandler resultSetHandler, ResultMap resultMap, ResultSetWrapper rsw, RowBounds rowBounds) {
this.resultSetHandler = resultSetHandler;
this.resultMap = resultMap;
this.rsw = rsw;
this.rowBounds = rowBounds;
}
```
- 大体瞄下每个属性的意思。下面,每个方法,胖友会更好的理解每个属性。
### 6.1.2 CursorStatus
CursorStatus ,是 DefaultCursor 的内部枚举类。代码如下:
```
// DefaultCursor.java
private enum CursorStatus {
/**
* A freshly created cursor, database ResultSet consuming has not started
*/
CREATED,
/**
* A cursor currently in use, database ResultSet consuming has started
*/
OPEN,
/**
* A closed cursor, not fully consumed
*
* 已关闭,并未完全消费
*/
CLOSED,
/**
* A fully consumed cursor, a consumed cursor is always closed
*
* 已关闭,并且完全消费
*/
CONSUMED
}
```
### 6.1.3 isOpen
```
// DefaultCursor.java
@Override
public boolean isOpen() {
return status == CursorStatus.OPEN;
}
```
### 6.1.4 isConsumed
```
// DefaultCursor.java
@Override
public boolean isConsumed() {
return status == CursorStatus.CONSUMED;
}
```
### 6.1.5 iterator
`#iterator()` 方法,获取迭代器。代码如下:
```
// DefaultCursor.java
@Override
public Iterator<T> iterator() {
// 如果已经获取,则抛出 IllegalStateException 异常
if (iteratorRetrieved) {
throw new IllegalStateException("Cannot open more than one iterator on a Cursor");
}
if (isClosed()) {
throw new IllegalStateException("A Cursor is already closed.");
}
// 标记已经获取
iteratorRetrieved = true;
return cursorIterator;
}
```
- 通过 `iteratorRetrieved` 属性,保证有且仅返回一次 `cursorIterator` 对象。
### 6.1.6 ObjectWrapperResultHandler
ObjectWrapperResultHandler DefaultCursor 的内部静态类,实现 ResultHandler 接口,代码如下:
```
// DefaultCursor.java
private static class ObjectWrapperResultHandler<T> implements ResultHandler<T> {
/**
* 结果对象
*/
private T result;
@Override
public void handleResult(ResultContext<? extends T> context) {
// <1> 设置结果对象
this.result = context.getResultObject();
// <2> 暂停
context.stop();
}
}
```
- `<1>` 处,暂存 [「3.1 DefaultResultSetHandler」](https://svip.iocoder.cn/MyBatis/executor-4/#) 处理的 ResultSet 的**当前行**的结果。
- `<2>` 处,通过调用 `ResultContext#stop()` 方法,暂停 DefaultResultSetHandler 在向下遍历下一条记录,从而实现每次在调用 `CursorIterator#hasNext()` 方法,只遍历一行 ResultSet 的记录。如果胖友有点懵逼,可以在看看 `DefaultResultSetHandler#shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds)` 方法,代码如下:
```
// DefaultCursor.java
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}
```
### 6.1.7 CursorIterator
CursorIterator DefaultCursor 的内部类,实现 Iterator 接口,游标的迭代器实现类。代码如下:
```
// DefaultCursor.java
private class CursorIterator implements Iterator<T> {
/**
* Holder for the next object to be returned
*
* 结果对象,提供给 {@link #next()} 返回
*/
T object;
/**
* Index of objects returned using next(), and as such, visible to users.
* 索引位置
*/
int iteratorIndex = -1;
@Override
public boolean hasNext() {
// <1> 如果 object 为空,则遍历下一条记录
if (object == null) {
object = fetchNextUsingRowBound();
}
// <2> 判断 object 是否非空
return object != null;
}
@Override
public T next() {
// <3> Fill next with object fetched from hasNext()
T next = object;
// <4> 如果 next 为空,则遍历下一条记录
if (next == null) {
next = fetchNextUsingRowBound();
}
// <5> 如果 next 非空,说明有记录,则进行返回
if (next != null) {
// <5.1> 置空 object 对象
object = null;
// <5.2> 增加 iteratorIndex
iteratorIndex++;
// <5.3> 返回 next
return next;
}
// <6> 如果 next 为空,说明没有记录,抛出 NoSuchElementException 异常
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove element from Cursor");
}
}
```
- ```
#hasNext()
```
方法,判断是否有
下一个
结果对象:
- `<1>` 处,如果 `object` 为空,则调用 `#fetchNextUsingRowBound()` 方法,遍历下一条记录。也就是说,该方法是先获取下一条记录,然后在判断是否存在下一条记录。实际上,和 `java.util.ResultSet` 的方式,是一致的。如果再调用一次该方法,则不会去遍历下一条记录。关于 `#fetchNextUsingRowBound()` 方法,详细解析,见 [「6.1.8 fetchNextUsingRowBound」](https://svip.iocoder.cn/MyBatis/executor-4/#) 。
- `<2>` 处,判断 `object` 非空。
- ```
#next()
```
方法,获得
下一个
结果对象:
- `<3>` 处,先记录 `object` 到 `next` 中。为什么要这么做呢?继续往下看。
- `<4>` 处,如果 `next` 为空有两种可能性1使用方未调用 `#hasNext()` 方法2调用 `#hasNext()` 方法,发现没下一条,还是调用了 `#next()` 方法。如果 `next()` 方法为空,通过“再次”调用 `#fetchNextUsingRowBound()` 方法,去遍历下一条记录。
- ```
<5>
```
处,如果
```
next
```
非空,说明有记录,则进行返回。
- `<5.1>` 处,置空 `object` 对象。
- `<5.2>` 处,增加 `iteratorIndex` 。
- `<5.3>` 处,返回 `next` 。如果 `<3>` 处,不进行 `next` 的赋值,如果 `<5.1>` 处的置空,此处就无法返回 `next` 了。
- `<6>` 处,如果 `next` 为空,说明没有记录,抛出 NoSuchElementException 异常。
### 6.1.8 fetchNextUsingRowBound
`#fetchNextUsingRowBound()` 方法,遍历下一条记录。代码如下:
```
// DefaultCursor.java
protected T fetchNextUsingRowBound() {
// <1> 遍历下一条记录
T result = fetchNextObjectFromDatabase();
// 循环跳过 rowBounds 的索引
while (result != null && indexWithRowBound < rowBounds.getOffset()) {
result = fetchNextObjectFromDatabase();
}
// 返回记录
return result;
}
```
- `<1>` 处,调用 `#fetchNextObjectFromDatabase()` 方法,遍历下一条记录。代码如下:
```
// DefaultCursor.java
protected T fetchNextObjectFromDatabase() {
// <1> 如果已经关闭,返回 null
if (isClosed()) {
return null;
}
try {
// <2> 设置状态为 CursorStatus.OPEN
status = CursorStatus.OPEN;
// <3> 遍历下一条记录
if (!rsw.getResultSet().isClosed()) {
resultSetHandler.handleRowValues(rsw, resultMap, objectWrapperResultHandler, RowBounds.DEFAULT, null);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
// <4> 复制给 next
T next = objectWrapperResultHandler.result;
// <5> 增加 indexWithRowBound
if (next != null) {
indexWithRowBound++;
}
// No more object or limit reached
// <6> 没有更多记录,或者到达 rowBounds 的限制索引位置,则关闭游标,并设置状态为 CursorStatus.CONSUMED
if (next == null || getReadItemsCount() == rowBounds.getOffset() + rowBounds.getLimit()) {
close();
status = CursorStatus.CONSUMED;
}
// <7> 置空 objectWrapperResultHandler.result 属性
objectWrapperResultHandler.result = null;
// <8> 返回下一条结果
return next;
}
```
- `<1>` 处,调用 `#isClosed()` 方法,判断是否已经关闭。若是,则返回 `null` 。代码如下:
```
// DefaultCursor.java
private boolean isClosed() {
return status == CursorStatus.CLOSED || status == CursorStatus.CONSUMED;
}
```
- `<2>` 处,设置状态为 `CursorStatus.OPEN` 。
- `<3>` 处,调用 `DefaultResultSetHandler#handleRowValues(...)` 方法,遍历下一条记录。也就是说,回到了 [[「3.1.2.2 handleRowValues」](https://svip.iocoder.cn/MyBatis/executor-4/#) 的流程。遍历的下一条件记录,会暂存到 `objectWrapperResultHandler.result` 中。
- `<4>` 处,复制给 `next` 。
- `<5>` 处,增加 `indexWithRowBound` 。
- `<6>` 处,没有更多记录,或者到达 `rowBounds` 的限制索引位置,则关闭游标,并设置状态为 `CursorStatus.CONSUMED` 。其中,涉及到的方法,代码如下:
```
// DefaultCursor.java
private int getReadItemsCount() {
return indexWithRowBound + 1;
}
@Override
public void close() {
if (isClosed()) {
return;
}
// 关闭 ResultSet
ResultSet rs = rsw.getResultSet();
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
// ignore
} finally {
// 设置状态为 CursorStatus.CLOSED
status = CursorStatus.CLOSED;
}
}
```
- `<7>` 处,置空 `objectWrapperResultHandler.result` 属性。
- `<8>` 处,返回 `next` 。
# 666. 彩蛋
卧槽,太他喵的长了。写的我的 MWeb( 写作编辑器 )都一卡一卡的。同时,写的有点崩溃,可能有些细节描述的不到位,如果有错误,或者不好理解的地方,记得给我星球留言,多多交流。
参考和推荐如下文章:
- 祖大俊 [《Mybatis3.4.x技术内幕二十一参数设置、结果封装、级联查询、延迟加载原理分析》](https://my.oschina.net/zudajun/blog/747283)
- 徐郡明 [《MyBatis 技术内幕》](https://item.jd.com/12125531.html) 的 [「3.3 ResultSetHandler」](https://svip.iocoder.cn/MyBatis/executor-4/#) 小节