# 精尽 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 columnNames = new ArrayList<>(); /** * 字段的 Java Type 的数组 */ private final List classNames = new ArrayList<>(); /** * 字段的 JdbcType 的数组 */ private final List jdbcTypes = new ArrayList<>(); private final Map, TypeHandler>> typeHandlerMap = new HashMap<>(); private final Map> mappedColumnNamesMap = new HashMap<>(); private final Map> 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:字段的名字 * KEY2:Java 属性类型 */ private final Map, 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, 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> mappedColumnNamesMap = new HashMap<>(); /** * 无 mapped 的字段的名字的映射 * * 和 {@link #mappedColumnNamesMap} 相反 */ private final Map> unMappedColumnNamesMap = new HashMap<>(); private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException { List mappedColumnNames = new ArrayList<>(); List unmappedColumnNames = new ArrayList<>(); // <1> 将 columnPrefix 转换成大写,并拼接到 resultMap.mappedColumns 属性上 final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH); final Set 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 columnNames, String prefix)` 方法,拼接到 `resultMap.mappedColumns` 属性上。代码如下: ``` // ResultSetWrapper.java private Set prependPrefixes(Set columnNames, String prefix) { // 直接返回 columnNames ,如果符合如下任一情况 if (columnNames == null || columnNames.isEmpty() || prefix == null || prefix.length() == 0) { return columnNames; } // 拼接前缀 prefix ,然后返回 final Set prefixed = new HashSet<>(); for (String columnName : columnNames) { prefixed.add(prefix + columnName); } return prefixed; } ``` - 当然,可能有胖友,跟我会懵逼,可能已经忘记什么是 `resultMap.mappedColumns` 。我们来举个示例: ``` ``` - 此处的 `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 getMappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException { // 获得对应的 mapped 数组 List 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 getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException { // 获得对应的 unMapped 数组 List 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 泛型 * @return 结果数组 */ List handleResultSets(Statement stmt) throws SQLException; /** * 处理 {@link java.sql.ResultSet} 成 Cursor 对象 * * @param stmt Statement 对象 * @param 泛型 * @return Cursor 对象 */ Cursor 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 nestedResultObjects = new HashMap<>(); private final Map ancestorObjects = new HashMap<>(); private Object previousRowValue; // multiple resultsets // 存储过程相关的多 ResultSet 涉及的属性,可以暂时忽略 private final Map nextResultMaps = new HashMap<>(); private final Map> pendingRelations = new HashMap<>(); // Cached Automappings /** * 自动映射的缓存 * * KEY:{@link ResultMap#getId()} + ":" + columnPrefix * * @see #createRowKeyForUnmappedProperties(ResultMap, ResultSetWrapper, CacheKey, String) */ private final Map> 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 handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); // <1> 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象。而实际上,每个 Object 是 List 对象。 // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,multipleResults 最多就一个元素。 final List multipleResults = new ArrayList<>(); int resultSetCount = 0; // <2> 获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象 ResultSetWrapper rsw = getFirstResultSet(stmt); // <3> 获得 ResultMap 数组 // 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,resultMaps 就一个元素。 List 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 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 multipleResults)` 方法,如果是 `multipleResults` 单元素,则取首元素返回。代码如下: ``` // DefaultResultSetHandler.java private List collapseSingleResultList(List multipleResults) { return multipleResults.size() == 1 ? (List) multipleResults.get(0) : multipleResults; } ``` - 对于非存储过程的结果处理,都能符合 `multipleResults.size()` 。 #### 3.1.2.1 handleResultSet `#handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping)` 方法,处理 ResultSet ,将结果添加到 `multipleResults` 中。代码如下: ``` // DefaultResultSetHandler.java private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List 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 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 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()` 方法。 - 再例如,`