mybatis-ResultSetHandler
Posted siye1989
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis-ResultSetHandler相关的知识,希望对你有一定的参考价值。
1. 概述
本文,我们来分享 SQL 执行的第四部分,SQL 执行后,响应的结果集 ResultSet 的处理,涉及 executor/resultset
、executor/result
、cursor
包。整体类图如下:
- 核心类是 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
属性,被包装的 ResultSet 对象。columnNames
、classNames
、jdbcTypes
属性,在<1>
处,通过遍历 ResultSetMetaData 的字段们,从而解析出来。
2.2 getTypeHandler
// ResultSetWrapper.java
|
<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
|
-
<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
|
3.1 DefaultResultSetHandler
老艿艿:保持冷静,DefaultResultSetHandler 有小 1000 行的代码。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler
,实现 ResultSetHandler 接口,默认的 ResultSetHandler 实现类。
3.1.1 构造方法
// DefaultResultSetHandler.java
|
- 属性比较多,我们看重点的几个。
resultHandler
属性,ResultHandler 对象。用户指定的用于处理结果的处理器,一般情况下,不设置。详细解析,见 「5. ResultHandler」 和 「3.1.2.3.3 storeObject」 。autoMappingsCache
属性,自动映射的缓存。其中,KEY 为@link ResultMap#getId() + ":" + columnPrefix
。详细解析,见 「3.1.2.3.2.4 applyAutomaticMappings」 。
3.1.2 handleResultSets
#handleResultSets(Statement stmt)
方法,处理 java.sql.ResultSet
结果集,转换成映射的对应结果。代码如下:
// DefaultResultSetHandler.java
|
- 这个方法,不仅仅支持处理 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」 。<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
|
<1>
处,暂时忽略,因为只有存储过程的情况,调用该方法,parentMapping
为非空。<2>
处,如果没有自定义的resultHandler
,则创建默认的 DefaultResultHandler 对象。<3>
处,调用#handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
方法,处理 ResultSet 返回的每一行 Row 。详细解析,见 「3.1.2.2 handleRowValues」 。-
【特殊】
<4>
处,使用默认的 DefaultResultHandler 对象,最终会将defaultResultHandler
的处理的结果,到multipleResults
中。而使用自定义的resultHandler
,不会添加到multipleResults
中。当然,因为自定义的resultHandler
对象,是作为一个对象传入,所以在其内部,还是可以存储结果的。例如:- 感兴趣的胖友,可以看看 《mybatis 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
|
- 分成嵌套映射和简单映射的两种情况。
<2>
处理嵌套映射的情况:<2.1>
处,调用#handleRowValuesForSimpleResultMap(...)
方法,处理简单映射的结果。详细解析,见 「3.1.2.3 handleRowValuesForSimpleResultMap 简单映射」 。
-
<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 嵌套映射」 。
-
3.1.2.3 handleRowValuesForSimpleResultMap 简单映射
#handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
方法,处理简单映射的结果。代码如下:
// DefaultResultSetHandler.java
|
<1>
处,创建 DefaultResultContext 对象。详细解析,胖友先跳到 「4. ResultContext」 中,看完就回来。-
<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分页原理》 ,解释的非常不错。
- 关于
-
<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」 。 <5>
处,调用#getRowValue(...)
方法,根据最终确定的 ResultMap 对 ResultSet 中的该行记录进行映射,得到映射后的结果对象。详细解析,等下看 「3.1.2.3.2 getRowValue」 。<6>
处,调用#storeObject(...)
方法,将映射创建的结果对象添加到ResultHandler.resultList
中保存。详细解析,等下看 「3.1.2.3.3 storeObject」 。
3.1.2.3.1 resolveDiscriminatedResultMap
#resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
方法,根据该行记录以及 ResultMap.discriminator
,决定映射使用的 ResultMap 对象。代码如下:
// DefaultResultSetHandler.java
|
- 对于大多数情况下,大家不太会使用 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
|
<1>
处,创建 ResultLoaderMap 对象。延迟加载相关。<2>
处,调用#createResultObject(...)
方法,创建映射后的结果对象。详细解析,见 「3.1.2.3.2.1 createResultObject」 。?? 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」 。 <7>
处,调用#applyPropertyMappings(...)
方法,映射 ResultMap 中明确映射的列。代码有点长,所以,详细解析,见 「3.1.2.3.2.4 applyPropertyMappings」 。<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
|
<1>
处,useConstructorMappings
,表示是否使用构造方法创建该结果对象。而此处,将其重置为false
。<2>
处,调用#createResultObject(...)
方法,创建映射后的结果对象。详细解析,往下看。?? 再次 mmp ,调用链太长了。<3>
处,如果有内嵌的查询,并且开启延迟加载,则调用ProxyFactory#createProxy(...)
方法,创建结果对象的代理对象。详细解析,见 《精尽 MyBatis 源码分析 —— SQL 执行(五)之延迟加载》 。<4>
处,判断是否使用构造方法创建该结果对象,并设置到useConstructorMappings
中。
#createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
方法,创建映射后的结果对象。代码如下:
// DefaultResultSetHandler.java
|
- 分成四种创建结果对象的情况。
-
<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」 。
<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
|
- 关于这个方法,胖友可以调试
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 执行(五)之延迟加载》 。
3.1.2.3.2.3 applyAutomaticMappings
#createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
方法,创建映射后的结果对象。代码如下:
// DefaultResultSetHandler.java
|
-
<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
即可。 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
|
- 虽然代码比较长,但是逻辑很简单。胖友自己瞅瞅。
-
在
<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)
方法,获得嵌套查询的值。详细解析,见 「」 。
- 在
3.1.2.3.2.5 getNestedQueryMappingValue 嵌套查询
#getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
方法,获得嵌套查询的值。代码如下:
// DefaultResultSetHandler.java
|
- 和 「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 执行(五)之延迟加载》 。<2.2>
处,返回已定义DEFERED
。- ========== 有缓存 ==========
<3.1>
处,创建 ResultLoader 对象。<3.2>
处,如果要求延迟加载,则延迟加载。<3.2.1>
处,调用ResultLoader#addLoader(...)
方法,如果该属性配置了延迟加载,则将其添加到ResultLoader.loaderMap
中,等待真正使用时再执行嵌套查询并得到结果对象。详细解析,见 《精尽 MyBatis 源码分析 —— SQL 执行(五)之延迟加载》 。
<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
|
- 逻辑比较简单,认真看下注释,特别是
<x>
处。
3.1.2.4 handleRowValuesForNestedResultMap 嵌套映射
可能胖友对嵌套映射的概念不是很熟悉,胖友可以调试 AncestorRefTest#testAncestorRef()
这个单元测试方法。
老艿艿:本小节,还是建议看 《MyBatis 技术内幕》 的 「3.3.4 嵌套映射」 小节。因为,它提供了比较好的这块逻辑的原理讲解,并且配置了大量的图。
?? 精力有限,后续补充哈。
?? 实际是,因为艿艿比较少用嵌套映射,所以对这块逻辑,不是很感兴趣。
#handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
方法,处理嵌套映射的结果。代码如下:
// DefaultResultSetHandler.java
|
- TODO 9999 状态不太好,有点写不太明白。不感兴趣的胖友,可以跳过。感兴趣的胖友,可以看看 《MyBatis 技术内幕》 的 「3.3.4 嵌套映射」 小节。
3.1.3 handleCursorResultSets
#handleCursorResultSets(Statement stmt)
方法,处理 java.sql.ResultSet
成 Cursor 对象。代码如下:
// DefaultResultSetHandler.java
|
- 最终,创建成 DefaultCursor 对象。详细解析,见 「6. DefaultCursor」 。
- 可能很多人没用 MyBatis Cursor 功能,所以可以看看 《Mybatis 3.4.0 Cursor的使用》 。
4. ResultContext
老艿艿:这个类,大体看看每个方法的用途,结合上文一起理解即可。
org.apache.ibatis.session.ResultContext
,结果上下文接口。代码如下:
// ResultContext.java
|
4.1 DefaultResultContext
org.apache.ibatis.executor.result.DefaultResultContext
,实现 ResultContext 接口,默认的 ResultContext 的实现类。代码如下:
// DefaultResultContext.java
|
5. ResultHandler
org.apache.ibatis.session.ResultHandler
,结果处理器接口。代码如下:
// ResultHandler.java
|
5.1 DefaultResultHandler
org.apache.ibatis.executor.result.DefaultResultHandler
,实现 ResultHandler 接口,默认的 ResultHandler 的实现类。代码如下:
// DefaultResultHandler.java
|
- 核心代码就是
<1>
处,将当前结果,添加到结果数组中。
5.2 DefaultMapResultHandler
该类在 session
包中实现,我们放在会话模块的文章中,详细解析。
6. Cursor
org.apache.ibatis.cursor.Cursor
,继承 Closeable、Iterable 接口,游标接口。代码如下:
// Cursor.java
|
6.1 DefaultCursor
org.apache.ibatis.cursor.defaults.DefaultCursor
,实现 Cursor 接口,默认 Cursor 实现类。
6.1.1 构造方法
// DefaultCursor.java
|
- 大体瞄下每个属性的意思。下面,每个方法,胖友会更好的理解每个属性。
6.1.2 CursorStatus
CursorStatus ,是 DefaultCursor 的内部枚举类。代码如下:
// DefaultCursor.java
|
6.1.3 isOpen
// DefaultCursor.java
|
6.1.4 isConsumed
// DefaultCursor.java
|
6.1.5 iterator
#iterator()
方法,获取迭代器。代码如下:
// DefaultCursor.java
|
- 通过
iteratorRetrieved
属性,保证有且仅返回一次cursorIterator
对象。
6.1.6 ObjectWrapperResultHandler
ObjectWrapperResultHandler ,DefaultCursor 的内部静态类,实现 ResultHandler 接口,代码如下:
// DefaultCursor.java
|
<1>
处,暂存 「3.1 DefaultResultSetHandler」 处理的 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
|
#hasNext()
方法,判断是否有下一个结果对象:<1>
处,如果object
为空,则调用#fetchNextUsingRowBound()
方法,遍历下一条记录。也就是说,该方法是先获取下一条记录,然后在判断是否存在下一条记录。实际上,和java.util.ResultSet
的方式,是一致的。如果再调用一次该方法,则不会去遍历下一条记录。关于#fetchNextUsingRowBound()
方法,详细解析,见 「6.1.8 fetchNextUsingRowBound」 。<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
|
<1>
处,调用#fetchNextObjectFromDatabase()
方法,遍历下一条记录。代码如下:
// DefaultCursor.java
|
-
<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」 的流程。遍历的下一条件记录,会暂存到objectWrapperResultHandler.result
中。<4>
处,复制给next
。<5>
处,增加indexWithRowBound
。-
<6>
处,没有更多记录,或者到达rowBounds
的限制索引位置,则关闭游标,并设置状态为CursorStatus.CONSUMED
。其中,涉及到的方法,代码如下:// DefaultCursor.java
private int getReadItemsCount()
return indexWithRowBound + 1;
<7>
处,置空objectWrapperResultHandler.result
属性。<8>
处,返回next
。
以上是关于mybatis-ResultSetHandler的主要内容,如果未能解决你的问题,请参考以下文章