Mybatis源码分析之结果集处理
解析封装
ResultMap 是和结果集相关的东西,最初在解析 XML 的时候,于 parseStatementNode 方法中,针对每一个 select 节点进行解析,转换为 MappedStatement(类似 Spring 的 bean 配置和 BeanDefinition 的关系)。
在 MapperBuilderAssistant 的 addMappedStatement 方法中,构建完statementBuilder,会调用 setStatementResultMap 方法给其设置 ResultMap。
setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
private void setStatementResultMap(
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
MappedStatement.Builder statementBuilder) {
resultMap = applyCurrentNamespace(resultMap, true);
List<ResultMap> resultMaps = new ArrayList<ResultMap>();
if (resultMap != null) {
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
try {
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("Could not find result map " + resultMapName, e);
}
}
} else if (resultType != null) {
ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
configuration,
statementBuilder.id() + "-Inline",
resultType,
new ArrayList<ResultMapping>(),
null);
resultMaps.add(inlineResultMapBuilder.build());
}
statementBuilder.resultMaps(resultMaps);
statementBuilder.resultSetType(resultSetType);
}
首先检查 resultMap,根据名称去 configuration 中取出对应的 resultMap 放到集合中;
如果 resultMap 不存在,就检查 resultType ,然后利用这个 resultType 构造一个 resultMap,如果两个都没有,那就走着瞧(resultMaps集合为空)。
中期调用
在 PreparedStatementHandler 的 query 方法中:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
而 resultSetHandler 于基类 BaseStatementHandler 中构造:
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
根据是否有嵌套的 ResultMaps 来确定创建 NestedResultSetHandler 还是 FastResultSetHandler:
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ? new NestedResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql,
rowBounds) : new FastResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
影响这个判断的有几个点:
- 解析时期
public Builder resultMaps(List<ResultMap> resultMaps) {
mappedStatement.resultMaps = resultMaps;
for (ResultMap resultMap : resultMaps) {
mappedStatement.hasNestedResultMaps = mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps();
}
return this;
}
- getBoundSql时期
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
后期处理
NestedResultSetHandler继承自FastResultSetHandler ,二者在handleResultSets是一致的(只是重写了 handleRowValues 等方法):
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<Object>();
final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
int resultSetCount = 0;
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;
}
}
}
// 验证是否定义了resultType或者resultMap
validateResultMapsCount(rs, resultMapCount);
while (rs != null && resultMapCount > resultSetCount) {
final ResultMap resultMap = resultMaps.get(resultSetCount);
ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
rs = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
return collapseSingleResultList(multipleResults);
}
处理模板
- validateResultMapsCount
resultMapCount 就是之前解析时构造的那个 ResultMap 集合的 size,如果没有配置 ResultMap 或 ResultType,这里就要报错了:
A query was run and no Result Maps were found for the Mapped Statement ……
- handleResultSet
传入构建的List类型的集合 multipleResults,调用 handleRowValues 处理从 ResultSet 获取的结果集。
- collapseSingleResultList
这个就不说了。
细节分析
FastResultSetHandler 用得相对较多,这里只针对其 handleRowValues 处理结果行进行分析:
protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
final DefaultResultContext resultContext = new DefaultResultContext();
skipRows(rs, rowBounds);
while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
callResultHandler(resultHandler, resultContext, rowValue);
}
}
1、RowBounds 与 分页
skipRows 就是跳过行,基于查询结果的分页使用,其中会直接调用 ResultSet 的 absolute 方法:
rs.absolute(rowBounds.getOffset());
而 shouldProcessMoreRows 方法就是判断是否应该获取更多的行:
protected boolean shouldProcessMoreRows(ResultSet rs, ResultContext context, RowBounds rowBounds) throws SQLException {
return !context.isStopped() && rs.next() && context.getResultCount() < rowBounds.getLimit();
}
也对应了分页的limit字段,这种分页可以说是伪分页,查出来再分页。所以我们一般使用插件的形式来实现分页,基于sql的动态替换。
2、获取行数据
如果应该获取更多的行,就会逐行处理结果:
protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(resultObject);
boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
foundValues = applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache) || foundValues;
}
final List<String> mappedColumnNames = resultColumnCache.getMappedColumnNames(resultMap, null);
foundValues = applyPropertyMappings(rs, resultMap, mappedColumnNames, metaObject, lazyLoader, null) || foundValues;
foundValues = (lazyLoader != null && lazyLoader.size() > 0) || foundValues;
resultObject = foundValues ? resultObject : null;
return resultObject;
}
return resultObject;
}
主要就是根据相关参数创建结果对象,这里又分多钟情况:
- 原始对象的处理(如 Integer 等基本类型对象)
利用 ResultMap 中存储的返回结果类型信息,直接返回相应的值。
- 非原始对象(是自定义的对象)处理
运用反射构造实体对象(相当于一个带有零值的初始对象)。然后用该对象构造一个 MetaObject ,并查看是否需要根据映射自动填充对象。
如果需要通过自动映射来填充,取出 List 类型的属性名,根据这个同 SQL 中查出来一致的字段名,去 ResultSet 中拿出来然后填充到对象中,相关代码如下:
final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
if (property != null) {
final Class<?> propertyType = metaObject.getSetterType(property);
if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
final TypeHandler<?> typeHandler = resultColumnCache.getTypeHandler(propertyType, columnName);
final Object value = typeHandler.getResult(rs, columnName);
if (value != null || configuration.isCallSettersOnNulls()) { // issue #377, call setter on nulls
metaObject.setValue(property, value);
foundValues = true;
}
}
}
这个处理完后,才是非映射列的处理。
3、结果映射
resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来, 并在一些情形下允许你做一些 JDBC 不支持的事情。
在 handleResultSets 方法中,取 ResultSet 元信息构造了一个 ResultColumnCache:
ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
后面在映射列的时候分别调用了 getUnmappedColumnNames 和 getMappedColumnNames 方法来取列集合,对应于 ResultColumnCache 的 unMappedColumnNamesMap 和 mappedColumnNamesMap。如果集合为空,将调用下面的方法来构造:
private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
List<String> mappedColumnNames = new ArrayList<String>();
List<String> unmappedColumnNames = new ArrayList<String>();
final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
for (String columnName : columnNames) {
final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
if (mappedColumns.contains(upperColumnName)) {
mappedColumnNames.add(upperColumnName);
} else {
unmappedColumnNames.add(columnName);
}
}
mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}
通常情况下,loadMappedAndUnmappedColumnNames 只会调用一次(多行结果的列名是一样的),所以 ResultColumnCache 名副其实。
它区分是否是满足映射的列名是根据 mappedColumns 集合中是否有来判断的,而 mappedColumns 对应于 ResultMap 配置中的 column 属性。
所以自动映射针对的是没有配置 ResultMap 而使用了 ResultType 的情况;或者配置并使用了 ResultMap,但是 ResultMap 中的 column 属性集合不包含实际SQL字段名,也就是对不上的情况。这个时候是需要直接操作这个字段名,可能需要去除下划线等基于规则的演变,最后通过 MetaObject 来赋值。
那么非自动映射呢?就是配置并使用了 ResultMap,这个简单多了,直接按你配置的来,取 ResultMap 属性配置下的 property 名称,这个就是你实际的对象属性名,然后也是通过 MetaObject 来赋值。
分析到这里,已经比较明朗了,整体的脉络也在脑海中勾勒出来了。ResultMap 实际远不止这些,至于高级功能到时候用起来再具体分析吧。
Mybatis源码分析之结果集处理的更多相关文章
- MyBatis 源码分析系列文章合集
1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...
- MyBatis源码分析-MyBatis初始化流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- MyBatis源码分析-SQL语句执行的完整流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- MyBatis源码分析(4)—— Cache构建以及应用
@(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...
- 【MyBatis源码分析】select源码分析及小结
示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...
- Mybatis源码分析-BaseExecutor
根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下Base ...
- MyBatis 源码分析 - 插件机制
1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...
- MyBatis 源码分析 - 缓存原理
1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...
- MyBatis 源码分析 - 内置数据源
1.简介 本篇文章将向大家介绍 MyBatis 内置数据源的实现逻辑.搞懂这些数据源的实现,可使大家对数据源有更深入的认识.同时在配置这些数据源时,也会更清楚每种属性的意义和用途.因此,如果大家想知其 ...
随机推荐
- JS中的匿名函数自执行、函数声明与函数表达式
先看一段jQuery源码中匿名函数自执行的例子: (function( window, undefined ) { // jquery code })(window); 另外一种常见的写法: +fun ...
- CS48 D BIT
统计一个点对应的和它严格右下方的点,点对数量.由于数据规模很大,不能直接上二维的前缀和,先排一维序,然后用BIT维护前缀和即可. /** @Date : 2017-09-14 20:17:30 * @ ...
- 天梯赛 L2-010 排座位 (并查集)
布置宴席最微妙的事情,就是给前来参宴的各位宾客安排座位.无论如何,总不能把两个死对头排到同一张宴会桌旁!这个艰巨任务现在就交给你,对任何一对客人,请编写程序告诉主人他们是否能被安排同席. 输入格式: ...
- Django之kindeditor
1.什么是kindeditor? KindEditor是一套开源的HTML可视化编辑器,主要用于让用户在网站上获得所见即所得编辑效果,兼容IE.Firefox.Chrome.Safari.Opera等 ...
- 超级ping(多线程版)
发现学校公共wifi的ip段是10.1.0-255.0-255段的,还是之前的思路批量ping一波. 其实可以使用nmap的.但是脚本写都写了.是吧.你懂的. #!/usr/bin/env pytho ...
- ubuntu之一些安装配置的坑
前言 本博客记录自己使用ubuntu的一些错误和坑. ubuntu不支持yum下载安装机制 命令 sudo apt install yum 是可以安装yum的,但安装好后执行: $ yum insta ...
- python 协程嵌套
import asyncio import time now = lambda: time.time() async def do_some_work(x): print('Waiting: ', x ...
- openstack环境下的虚拟机通过浮动IP访问后能ping通外网IP不能ping通域名
1.环境简介 openstack环境下构造Ubuntu系统的VM,VM配置受管子网和自管子网,同时绑定浮动IP 2.通过浮动IP访问VM后,ping www.baidu.com失败,但是通过IP地址p ...
- [How to] 使用HBase协处理器---Endpoint客户端代码的实现
1.简介 不同于Observer协处理器,EndPoint由于需要同region进行rpc服务的通信,以及客户端出数据的归并,需要自行实现客户端代码. 基于[How to] 使用HBase协处理器-- ...
- 让R与Python共舞
转载:http://ices01.sinaapp.com/?p=129 R(又称R语言)是一款开源的跨平台的数值统计和数值图形化展现 工具.通俗点说,R是用来做统计和画图的.R拥有自己的脚本 ...