该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址Mybatis-Spring 源码分析 GitHub 地址Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

MyBatis的SQL执行过程

在前面一系列的文档中,我已经分析了 MyBatis 的基础支持层以及整个的初始化过程,此时 MyBatis 已经处于就绪状态了,等待使用者发号施令了

那么接下来我们来看看它执行SQL的整个过程,该过程比较复杂,涉及到二级缓存,将返回结果转换成 Java 对象以及延迟加载等等处理过程,这里将一步一步地进行分析:

MyBatis中SQL执行的整体过程如下图所示:

在 SqlSession 中,会将执行 SQL 的过程交由Executor执行器去执行,过程大致如下:

  1. 通过DefaultSqlSessionFactory创建与数据库交互的 SqlSession “会话”,其内部会创建一个Executor执行器对象
  2. 然后Executor执行器通过StatementHandler创建对应的java.sql.Statement对象,并通过ParameterHandler设置参数,然后执行数据库相关操作
  3. 如果是数据库更新操作,则可能需要通过KeyGenerator先设置自增键,然后返回受影响的行数
  4. 如果是数据库查询操作,则需要将数据库返回的ResultSet结果集对象包装成ResultSetWrapper,然后通过DefaultResultSetHandler对结果集进行映射,最后返回 Java 对象

上面还涉及到一级缓存二级缓存延迟加载等其他处理过程

SQL执行过程(三)之ResultSetHandler

高能预警 ️ ️ ️ ️ ️ ️

DefaultResultSetHandler(结果集处理器)将数据库查询结果转换成 Java 对象是一个非常繁琐的过程,需要处理各种场景,如果继续往下看,请做好心理准备

在前面SQL执行过程一系列的文档中,已经详细地分析了在MyBatis的SQL执行过程中,SqlSession会话将数据库操作交由Executor执行器去完成,然后通过StatementHandler去执行数据库相关操作,并获取到数据库的执行结果

如果是数据库查询操作,则需要通过ResultSetHandler对查询返回的结果集进行映射处理,转换成对应的Java对象,算是SQL执行过程的最后一步,那么我们来看看MyBatis是如何完成这个繁杂的解析过程的

ResultSetHandler接口的实现类如下图所示:

先回顾一下ResultSetHandler在哪被调用,在PreparedStatementHandlerquery方法中,代码如下:

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行
ps.execute();
// 结果处理器并返回结果
return resultSetHandler.handleResultSets(ps);
}
  • 属性resultSetHandler默认为DefaultResultSetHandler对象,可以回到《SQL执行过程(二)之StatementHandler》BaseStatementHandler小节中的构造方法的第3步可以看到
  • 调用resultSetHandlerhandleResultSets(Statement stmt)方法,对结果集进行映射,转换成Java对象并返回

ResultSetWrapper

因为在DefaultResultSetHandler中,对ResultSet的操作更多的是它的ResultSetWrapper包装类,所以我们先来看看这个类

org.apache.ibatis.executor.resultset.ResultSetWrapperjava.sql.ResultSet的包装类,为DefaultResultSetHandler提供许多便捷的方法,直接来看它的代码

构造方法

public class ResultSetWrapper {

  /**
* ResultSet 对象
*/
private final ResultSet resultSet;
/**
* 类型处理器注册表
*/
private final TypeHandlerRegistry typeHandlerRegistry;
/**
* ResultSet 中每列的列名
*/
private final List<String> columnNames = new ArrayList<>();
/**
* ResultSet 中每列对应的 Java Type
*/
private final List<String> classNames = new ArrayList<>();
/**
* ResultSet 中每列对应的 Jdbc Type
*/
private final List<JdbcType> jdbcTypes = new ArrayList<>();
/**
* 记录每列对应的 TypeHandler 对象
* key:列名
* value:TypeHandler 集合
*/
private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
/**
* 记录了被映射的列名
* key:ResultMap 对象的 id {@link #getMapKey(ResultMap, String)}
* value:ResultMap 对象映射的列名集合
*/
private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
/**
* 记录了未映射的列名
* key:ResultMap 对象的 id {@link #getMapKey(ResultMap, String)}
* value:ResultMap 对象未被映射的列名集合
*/
private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>(); public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
super();
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.resultSet = rs;
// 获取 ResultSet 的元信息
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
// 获得列名或者通过 AS 关键字指定列名的别名
columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
// 获得该列对应的 Jdbc Type
jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
// 获得该列对应的 Java Type
classNames.add(metaData.getColumnClassName(i));
}
}
}
  • resultSet:被包装的ResultSet结果集对象
  • typeHandlerRegistry:类型处理器注册表,因为需要进行Java Type与Jdbc Type之间的转换
  • columnNames:结果集中的所有列名
  • classNames:结果集中的每列的对应的Java Type的名称
  • jdbcTypes:结果集中的每列对应的Jdbc Type
  • typeHandlerMap:结果集中每列对应的类型处理器
  • mappedColumnNamesMap:保存每个ResultMap对象中映射的列名集合,也就是我们在<resultMap />标签下的子标签配置的column属性
  • unMappedColumnNamesMap:保存每个ResultMap对象中未映射的列名集合,也就是没有在<resultMap />标签下配置过,但是查询结果返回了

在构造方法中,会初始化上面的columnNamesclassNamesjdbcTypes属性

getTypeHandler方法

getTypeHandler(Class<?> propertyType, String columnName):通过列名和Java Type获取对应的TypeHandler类型处理器,方法如下:

public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
TypeHandler<?> handler = null;
// 获取列名对应的类型处理器
Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
if (columnHandlers == null) {
columnHandlers = new HashMap<>();
typeHandlerMap.put(columnName, columnHandlers);
} else {
handler = columnHandlers.get(propertyType);
}
if (handler == null) {
// 获取该列对应的 Jdbc Type
JdbcType jdbcType = getJdbcType(columnName);
// 根据 Java Type 和 Jdbc Type 获取对应的 TypeHandler 类型处理器
handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
// Replicate logic of UnknownTypeHandler#resolveTypeHandler
// See issue #59 comment 10
if (handler == null || handler instanceof UnknownTypeHandler) {
// 从 ResultSet 中获取该列对应的 Java Type 的 Class 对象
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);
}
}
if (handler == null || handler instanceof UnknownTypeHandler) {
// 最差的情况,设置为 ObjectTypeHandler
handler = new ObjectTypeHandler();
}
// 将生成的 TypeHandler 存放在 typeHandlerMap 中
columnHandlers.put(propertyType, handler);
}
return handler;
}

大致逻辑如下:

  1. 先从Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap属性中获取类型处理器
  2. 如果从缓存中没有获取到,则尝试根据Jdbc Type和Java Type从typeHandlerRegistry注册表获取
  3. 如果还是没有获取到,则根据classNames中拿到结果集中该列的Java Type,然后在从typeHandlerRegistry注册表获取
  4. 还是没有获取到,则设置为ObjectTypeHandler
  5. 最后将其放入typeHandlerMap缓存中

loadMappedAndUnmappedColumnNames方法

loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix)方法,初始化mappedColumnNamesMapunMappedColumnNamesMap两个属性,分别为映射的列名和未被映射的列名,方法如下:

private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
List<String> mappedColumnNames = new ArrayList<>();
List<String> unmappedColumnNames = new ArrayList<>();
// <1> 获取配置的列名的前缀,全部大写
final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
/*
* <2> 获取 ResultMap 中配置的所有列名,并添加前缀
* 如果在 <select /> 上面配置的是 resultType 属性,则返回的是空集合,因为它生成的 ResultMap 只有 Java Type 属性
*/
final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
/*
* <3> 遍历数据库查询结果中所有的列名
* 将所有列名分为两类:是否配置了映射
*/
for (String columnName : columnNames) {
final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
if (mappedColumns.contains(upperColumnName)) {
mappedColumnNames.add(upperColumnName);
} else {
unmappedColumnNames.add(columnName);
}
}
// <4> 将上面两类的列名保存
mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}
  1. 获取配置的列名的前缀,全部大写,通常是没有配置的

  2. 获取ResultMap中配置的所有列名,并添加前缀

    如果在<select />上面配置的是resultType属性,则返回的是空集合,因为它创建的ResultMap对象中只有Java Type属性

  3. 遍历结果集中所有的列名,如果在<resultMap />标签中的子标签配置的column属性有包含这个列名,则属于映射的列名

  4. 否则就属于未被映射的列名

ResultSetHandler

org.apache.ibatis.executor.resultset.ResultSetHandler:结果集映射接口,代码如下:

public interface ResultSetHandler {
/**
* 处理 {@link java.sql.ResultSet} 成映射的对应的结果
*
* @param stmt Statement 对象
* @param <E> 泛型
* @return 结果数组
* @throws SQLException SQL异常
*/
<E> List<E> handleResultSets(Statement stmt) throws SQLException; /**
* 处理 {@link java.sql.ResultSet} 成 Cursor 对象
*
* @param stmt Statement 对象
* @param <E> 泛型
* @return Cursor 对象
* @throws SQLException SQL异常
*/
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException; /**
* 暂时忽略,和存储过程相关
*
* @param cs CallableStatement 对象
* @throws SQLException SQL异常
*/
void handleOutputParameters(CallableStatement cs) throws SQLException;
}

DefaultResultSetHandler

org.apache.ibatis.executor.resultset.DefaultResultSetHandler:实现ResultSetHandler接口,处理数据库的查询结果,对结果集进行映射,将结果转换成Java对象

由于该类嵌套的方法太多了,可能一个方法会有十几层的嵌套,所以本分不会进行全面的分析

因为我查看这个类的时候是从下面的方法一层一层往上看的,注释我全部添加了,所以可以参考我的注释一步一步查看

接下来的描述可能有点混乱,请按照我在方法前面表明的顺序进行查看,参考:DefaultResultSetHandler.java

先来看下DefaultResultSetHandler处理结果集的方法的流程图:

构造方法

public class DefaultResultSetHandler implements ResultSetHandler {
/**
* 延迟加载默认对象
*/
private static final Object DEFERRED = new Object();
/**
* 执行器
*/
private final Executor executor;
/**
* 全局配置对象
*/
private final Configuration configuration;
/**
* 本次查询操作对应的 MappedStatement 对象
*/
private final MappedStatement mappedStatement;
/**
* 分页对象
*/
private final RowBounds rowBounds;
/**
* 参数处理器,默认为 DefaultParameterHandler
*/
private final ParameterHandler parameterHandler;
/**
* 结果处理器,默认为 DefaultResultHandler
*/
private final ResultHandler<?> resultHandler;
/**
* SQL 相关信息
*/
private final BoundSql boundSql;
/**
* 类型处理器注册表
*/
private final TypeHandlerRegistry typeHandlerRegistry;
/**
* 对象实例工厂
*/
private final ObjectFactory objectFactory;
/**
* Reflector 工厂
*/
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
private final Map<String, ResultMapping> nextResultMaps = new HashMap<>();
private final Map<CacheKey, List<PendingRelation>> pendingRelations = new HashMap<>(); // Cached Automappings
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;
}
}
  • 上面的属性有点多,可以先根据注释进行理解,也可以在接下来的方法中逐步理解

1.handleResultSets方法

handleResultSets(Statement stmt)方法,处理结果集的入口

/**
* 1.处理结果集
*/
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); /*
* <1> 用于保存映射结果集得到的结果队形
* 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象,而实际上,每个 Object 是 List<Object> 对象
*/
final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0;
// <2> 获取 ResultSet 对象,并封装成 ResultSetWrapper
ResultSetWrapper rsw = getFirstResultSet(stmt); /*
* <3> 获得当前 MappedStatement 对象中的 ResultMap 集合,XML 映射文件中 <resultMap /> 标签生成的
* 或者 配置 "resultType" 属性也会生成对应的 ResultMap 对象
* 在 <select /> 标签配置 ResultMap 属性时,可以以逗号分隔配置多个,如果返回多个 ResultSet 则会一一映射,通常配置一个
*/
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
// <4> 如果有返回结果,但是没有 ResultMap 接收对象则抛出异常
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
/*
* <5> 完成结果集的映射,全部转换的 Java 对象
* 保存至 multipleResults 集合中,或者 this.resultHandler 中
*/
handleResultSet(rsw, resultMap, multipleResults, null);
// 获取下一个结果集
rsw = getNextResultSet(stmt);
// 清空 nestedResultObjects 集合
cleanUpAfterHandlingResultSet();
// 递增 resultSetCount 结果集数量
resultSetCount++;
} // <6> 获取 resultSets 多结果集属性的配置,存储过程中使用,暂时忽略
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
// 根据 resultSet 的名称,获取未处理的 ResultMapping
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
// 未处理的 ResultMap 对象
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
// 完成结果集的映射,全部转换的 Java 对象
handleResultSet(rsw, resultMap, null, parentMapping);
}
// 获取下一个结果集
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
} // <7> 如果是 multipleResults 单元素,则取首元素返回
return collapseSingleResultList(multipleResults);
}
  1. multipleResults用于保存映射结果集得到的结果队形,多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象,而实际上,每个 Object 是 List<Object> 对象

  2. 获取 ResultSet 对象,并封装成 ResultSetWrapper

  3. 获得当前 MappedStatement 对象中的 ResultMap 集合,XML 映射文件中<resultMap />标签生成的,或者 配置 "resultType" 属性也会生成对应的 ResultMap 对象

    <select /> 标签配置 ResultMap 属性时,可以以逗号分隔配置多个,如果返回多个 ResultSet 则会一一映射,通常配置一个

  4. 如果有返回结果,但是没有 ResultMap 接收对象则抛出异常

  5. 调用handleResultSet方法,完成结果集的映射,全部转换的 Java 对象,保存至 multipleResults 集合中,或者 this.resultHandler 中(用户自定的,通常不会)

  6. 获取 resultSets 多结果集属性的配置,存储过程中使用,暂时忽略,本文暂不分析

完成结果集映射的任务还是交给了2.handleResultSet方法

2.handleResultSet方法

handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping)方法,处理结果集

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
// <1> 暂时忽略,因为只有存储过程的情况时 parentMapping 为非空
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) { // <2>
// <2.1> 创建 DefaultResultHandler 默认结果处理器
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
// <2.2> 处理结果集,进行一系列的处理,完成映射,将结果保存至 DefaultResultHandler 中
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
// <2.3> 将结果集合添加至 multipleResults 中
multipleResults.add(defaultResultHandler.getResultList());
} else { // 用户自定义了 resultHandler,则结果都会保存在其中
// <3> 处理结果集,进行一系列的处理,完成映射,将结果保存至 DefaultResultHandler 中
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
// <4> 关闭结果集
closeResultSet(rsw.getResultSet());
}
}
  1. 暂时忽略,因为只有存储过程的情况时 parentMapping 为非空,查看上面的1.handleResultSets方法的第6

  2. 用户没有指定ResultHandler结果处理器

    1. 创建DefaultResultHandler默认结果处理器,就是使用一个List集合保存转换后的Java对象
    2. 调用handleRowValues方法,处理结果集,进行一系列的处理,完成映射,将结果保存至 DefaultResultHandler 中
    3. 将结果集合添加至 multipleResults
  3. 用户指定了自定义的ResultHandler结果处理器,和第2步的区别在于,处理后的Java对象不会保存在multipleResults 中,仅保存在ResultHandler中,用户可通过它获取

  4. 关闭 ResultSet 结果集对象

通常我们不会自定义结果处理器的,所以第4步本文暂不分析,我们来看到第2步,最终还是交给了3.handleRowValues方法

3.handleRowValues方法

handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)方法,处理结果集

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
/*
* <1> ResultMap 存在内嵌的 ResultMap
* 例如 <resultMap /> 标签中 <association /> 或者 <collection /> 都会创建对应的 ResultMap 对象
* 该对象的 id 会设置到 ResultMapping 的 nestedResultMapId 属性中,这就属于内嵌的 ResultMap
*/
if (resultMap.hasNestedResultMaps()) { // 存在
// <1.1> 如果不允许在嵌套语句中使用分页,则对 rowBounds 进行校验,设置了 limit 或者 offset 则抛出异常,默认允许
ensureNoRowBounds();
// <1.2> 校验要不要使用自定义的 ResultHandler,针对内嵌的 ResultMap
checkResultHandler();
// <1.3> 处理结果集,进行映射,生成返回结果,保存至 resultHandler 或者设置到 parentMapping 的对应属性中
// 这里会处理内嵌的 ResultMap
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// <2> 处理结果集,进行映射,生成返回结果,保存至 resultHandler 或者设置到 parentMapping 的对应属性中
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
  1. 如果当前 ResultMap 存在内嵌的 ResultMap

    例如 <resultMap /> 标签中 <association /> 或者 <collection /> 都会创建对应的 ResultMap 对象,该对象的 id 会设置到 ResultMappingnestedResultMapId 属性中,这就属于内嵌的 ResultMap

    1. 如果不允许在嵌套语句中使用分页,则对 rowBounds 进行校验,设置了 limit 或者 offset 则抛出异常,默认允许
    2. 校验要不要使用自定义的 ResultHandler,针对内嵌的 ResultMap
    3. 处理结果集,进行映射,生成返回结果,保存至 resultHandler 或者设置到 parentMapping(存储过程相关,本文暂不分析)的对应属性中,这里会对内嵌的 ResultMap 进行处理,调用handleRowValuesForNestedResultMap方法
  2. 处理结果集,进行映射,生成返回结果,保存至 resultHandler 或者设置到 parentMapping(存储过程相关,本文暂不分析)的对应属性中,调用handleRowValuesForSimpleResultMap方法

这里先来看到第2步中的4.handleRowValuesForSimpleResultMap方法,因为这个处理的情况相比第1步调用的方法简单些

4.handleRowValuesForSimpleResultMap方法

handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)方法,处理结果集(不含嵌套映射)

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
ResultHandler<?> resultHandler, RowBounds rowBounds,
ResultMapping parentMapping) throws SQLException {
// 默认的上下文对象,临时保存每一行的结果且记录返回结果数量
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
// <1> 根据 RowBounds 中的 offset 跳到到指定的记录
skipRows(resultSet, rowBounds);
// <2> 检测已经处理的行数是否已经达到上限(RowBounds.limit)以及 ResultSet 中是否还有可处理的记录
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
/*
* <3> 获取最终的 ResultMap
* 因为 ResultMap 可能使用到了 <discriminator /> 标签,需要根据不同的值映射不同的 ResultMap
* 如果存在 Discriminator 鉴别器,则根据当前记录选择对应的 ResultMap,会一直嵌套处理
*/
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// <4> 从结果集中获取到返回结果对象,进行映射,比较复杂,关键方法!!!
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// <5> 将返回结果对象保存至 resultHandler,或者设置到父对象 parentMapping 的对应属性中
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}

这里创建了一个DefaultResultContext保存结果的上下文对象,点击去你会发现有3个属性:

  • resultObject:暂存映射后的返回结果,因为结果集中可能有很多条数据
  • resultCount:记录经过 DefaultResultContext 暂存的对象个数
  • stopped:控制是否还进行映射
  1. 根据 RowBounds 中的 offset 跳到到结果集中指定的记录

  2. 检测已经处理的行数是否已经达到上限(RowBounds.limit)以及 ResultSet 中是否还有可处理的记录

  3. 调用resolveDiscriminatedResultMap方法,获取最终的 ResultMap

    因为 ResultMap 可能使用到了 <discriminator /> 标签,需要根据不同的值映射不同的 ResultMap

    如果存在 Discriminator 鉴别器,则根据当前记录选择对应的 ResultMap,会一直嵌套处理

  4. 调用getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)方法,从结果集中获取到返回结果对象,进行映射,比较复杂,关键方法!!!

  5. 调用storeObject方法,将返回结果对象保存至 resultHandler,或者设置到父对象 parentMapping(存储过程相关,本文暂不分析)的对应属性中

对于第345步的三个方法,我们一个一个来看

  • 4.1resolveDiscriminatedResultMap方法

  • 4.2getRowValue方法

  • 4.3storeObject方法

4.1resolveDiscriminatedResultMap方法

resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)方法,如果存在<discriminator />鉴别器,则进行处理,选择对应的 ResultMap,会一直嵌套处理

public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
throws SQLException {
// 记录已经处理过的 ResultMap 的 id
Set<String> pastDiscriminators = new HashSet<>();
// <1> 获取 ResultMap 中的 Discriminator 鉴别器,<discriminator />标签会被解析成该对象
Discriminator discriminator = resultMap.getDiscriminator();
while (discriminator != null) {
// <2> 获取当前记录中该列的值,通过类型处理器转换成了对应的类型
final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
// <3> 鉴别器根据该值获取到对应的 ResultMap 的 id
final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
if (configuration.hasResultMap(discriminatedMapId)) {
// <3.1> 获取到对应的 ResultMap
resultMap = configuration.getResultMap(discriminatedMapId);
// <3.2> 记录上一次的鉴别器
Discriminator lastDiscriminator = discriminator;
// <3.3> 获取到对应 ResultMap 内的鉴别器,可能鉴别器里面还有鉴别器
discriminator = resultMap.getDiscriminator();
// <3.4> 检测是否出现循环嵌套了
if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
break;
}
} else {
// <4> 鉴别结果没有对应的 ResultMap,则直接跳过
break;
}
}
// <5> 返回最终使用的 ResultMap 对象
return resultMap;
}
  1. 获取 ResultMap 中的 Discriminator 鉴别器,<discriminator /> 标签会被解析成该对象

  2. 调用getDiscriminatorValue方法,获取当前记录中该列的值,通过类型处理器转换成了对应的类型,方法如下:

    private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException {
    // 获取 <discriminator />标签对应的的 ResultMapping 对象
    final ResultMapping resultMapping = discriminator.getResultMapping();
    // 获取 TypeHandler 类型处理器
    final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
    // 通过 TypeHandler 从 ResultSet 中获取该列的值
    return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
    }
  3. Discriminator 鉴别器根据该值获取到对应的 ResultMap 的 id

    1. 存在对应的 ResultMap 对象,则获取到
    2. 记录上一次的鉴别器
    3. 获取到对应 ResultMap 内的鉴别器,可能鉴别器里面还有鉴别器
    4. 检测是否出现循环嵌套了
  4. Discriminator 鉴别结果没有对应的 ResultMap,则直接跳过

  5. 返回最终使用的 ResultMap 对象

4.2getRowValue方法

getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)方法,处理结果集

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
// <1> 保存延迟加载的集合
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// <2> 创建返回结果的实例对象(如果存在嵌套子查询且是延迟加载则为其创建代理对象,后续的延迟加载保存至 lazyLoader 中即可)
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); /*
* <3> 如果上面创建的返回结果的实例对象不为 null,并且没有对应的 TypeHandler 类型处理器,则需要对它进行赋值
* 例如我们返回结果为 java.lang.String 就不用了,因为上面已经处理且赋值了
*/
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// <3.1> 将返回结果的实例对象封装成 MetaObject,便于操作
final MetaObject metaObject = configuration.newMetaObject(rowValue);
// <3.2> 标记是否成功映射了任意一个属性,useConstructorMappings 表示是否在构造方法中使用了参数映射
boolean foundValues = this.useConstructorMappings;
// <3.3> 检测是否需要自动映射
if (shouldApplyAutomaticMappings(resultMap, false)) {
/*
* <3.4> 从结果集中将未被映射的列值设置到返回结果 metaObject 中
* 返回是否映射成功,设置了1个或以上的属性值
*/
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
/*
* <3.5> 从结果集中将 ResultMap 中需要映射的列值设置到返回结果 metaObject 中
* 返回是否映射成功,设置了1个或以上的属性值
*/
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
/*
* <3.6> 如果没有成功映射任意一个属性,则根据 returnInstanceForEmptyRow 全局配置(默认为false)返回空对象还是 null
*/
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
// <4> 返回该结果对象
return rowValue;
}
  1. 创建一个保存延迟加载的集合ResultLoaderMap对象lazyLoader,如果存在代理对象,创建的代理对象则需要通过它来执行需要延迟加载的方法,在后续会将到

    精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler的更多相关文章

    1. 精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载

      该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

    2. 精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler

      该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

    3. 精尽 MyBatis 源码分析 - SqlSession 会话与 SQL 执行入口

      该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

    4. 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)

      该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

    5. 精尽 MyBatis 源码分析 - 基础支持层

      该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

    6. 精尽MyBatis源码分析 - 插件机制

      该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

    7. 精尽MyBatis源码分析 - 文章导读

      该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

    8. MyBatis 源码篇-SQL 执行的流程

      本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...

    9. 精尽MyBatis源码分析 - MyBatis-Spring 源码分析

      该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

    随机推荐

    1. mysql 改变表结构 alter

      总结:alter添加栏位时,只需记住添加新栏位为第一列,用first;添加其他用,after 前一个栏位字段,如下例 1.需求:将新的栏位添加为第二列 添加前: 添加后: 参考:http://www. ...

    2. Linux常用操作命令大全

      0.新建操作:1.查看操作    2.删除操作 3.复制操作    4.移动操作:5.重命名操作: 6.解压压缩操作    7.上传文件工具    8.ln.file和touch命令 9.查找操作命令 ...

    3. 【应用服务 App Service】App Service证书导入,使用Key Vault中的证书

      问题描述 正常情况下,如果需要为应用服务安装SSL证书,可以在证书准备好的情况,通过门户上传即可,详细步骤可以参考微软官方文档(在 Azure 应用服务中添加 TLS/SSL 证书:https://d ...

    4. 深入了解Redis(8)-高可用方案

      生产环境中的redis基本都是多节点部署,本文只讨论redis高可用的三种方案,不涉及实际操作. 一.主从复制(一主一从,一主多从,级联结构) (图来源于网络) 一个Master,两个Slave,Sl ...

    5. Spring 5的最后一个特性版本5.3发布,4.3将于12月终止维护

      10月27日,Spring Framework团队宣布了5.3版本正式GA,Spring用户可以在repo.spring.io和Maven Central上获取到最新版本的依赖包. JDK的版本支持 ...

    6. Android Studio的第一次经历

      第一个简单APP的制作是从xml开始的,通过在java新建一个empty  activity,并在layout里找到对应的xml文件进行编写.每编写一个xml就要事先新建 一个对应的empty  ac ...

    7. JS中使用for-each遍历数组

      1 let array = [1, 3, 6, 8, 9, 0, 5]; 2 /* 3 index是数组索引 4 value代表数组的值 5 arr是指整个数组 6 */ 7 array.forEac ...

    8. SpringCloud gateway 过滤

      如果需要获取一张图片但服务器没有过滤图片请求地址时,每次请求图片都需要携带token等安全验证密钥,可到nacos配置网关(gateway)的security配置,可过滤掉你配置的url(可理解为白名 ...

    9. 【洛谷】P1009 阶乘之和——高精度算法

      题目描述 用高精度计算出S = 1! + 2! + 3! + - + n!  ( n ≤  50 ) S = 1! + 2! + 3! + - + n! ( n ≤ 50 ) 其中"!&qu ...

    10. read函数

      ssize_t read(int fildes, void *buf, size_t nbyte); 返回值: > 0: 实际读到的字节数 = 0: 读完数据(读文件, 管道, socket末尾 ...