MyBatis源码阅读(七) --- 查询结果集封装流程
Mybatis源码阅读系列文章:
MyBatis源码阅读(一) --- 源码阅读环境搭建
MyBatis源码阅读(二) --- 执行流程分析
MyBatis源码阅读(三) --- 配置信息的解析以及SqlSessionFactory构建过程
MyBatis源码阅读(四) --- SqlSession的创建过程
MyBatis源码阅读(五) ---Mapper接口的获取过程
MyBatis源码阅读(六) ---mapper方法具体执行流程分析
MyBatis源码阅读(七) --- 查询结果集封装流程
MyBatis源码阅读(八) --- Executor执行器
MyBatis源码阅读(九) --- 插件原理
MyBatis源码阅读(十) --- 一级缓存、二级缓存工作原理
MyBatis源码阅读(十一) --- MyBatis事务管理机制
MyBatis源码阅读(十二) --- Spring加载MyBatis过程
目录
一、概述
二、查询结果集封装流程
三、查询结果集封装流程
四、总结
一、概述
前面一篇文章我们分析了mapper方法具体的执行流程,跟踪代码到了resultSetHandler.handleResultSets()处理结果集这里,这也是Mybatis处理数据的最后一个步骤了。
试想一下,如果让我们自己组装结果,我们该如何实现呢,可以大概想一下。
如果是我们自己实现的话,有几个关键的步骤:
- 1、准备一个List集合存放结果集;
- 2、肯定要拿到我们在Mapper.xml中配置的resultType属性,拿到之后,通过反射,我们应该可以拿到对应的Class类;
- 3、拿到Class类之后,那么就可以拿到构造方法new一个空对象,属性都是空的,还没有赋值;
- 4、获取类有哪些属性,然后根据resultSetHandler和属性名称,像JDBC那样resultSet.getString()、
- resultSet.getInt()获取到查询的值,动态设置到前面创建的空对象中;
- 5、将此对象加入List结果集中,然后返回此集合或者集合的第一个元素;
那么Mybatis究竟是不是这样处理的呢,下面我们就来详细分析一下Mybatis是如何使用ResultSetHandler封装结果集的。
二、查询结果集封装流程
我们直接查看结果封装的开始入口:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();return resultSetHandler.handleResultSets(ps);
}
由此,进入到 resultSetHandler.handleResultSets(ps) 方法,而默认会进入到的DefaultResultSetHandler的handleResultSets方法:
//org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
public List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());//存放查询结果的集合 final List<Object> multipleResults = new ArrayList<>();//结果集的个数 int resultSetCount = 0;//第一步:封装ResultSetWrapperResultSetWrapper rsw = getFirstResultSet(stmt);//从mappedStatement中获取所有的ResultMapList<ResultMap> resultMaps = mappedStatement.getResultMaps();//resultMap的个数int resultMapCount = resultMaps.size();// 如果结果集有数据,但是没有定义返回的结果类型,就会报错//报错:A query was run and no Result Maps were found for the Mapped Statement.. It's likely that neither a Result Type nor a Result Map was specified.validateResultMapsCount(rsw, resultMapCount);while (rsw != null && resultMapCount > resultSetCount) {ResultMap resultMap = resultMaps.get(resultSetCount);//第二步:处理结果集handleResultSet(rsw, resultMap, multipleResults, null);//第三步:获取下一个结果集rsw = getNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount++;}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++;}}//第四步:判断multipleResults结果集长度是不是等于1,如果等于1返回集合第一个元素,否则返回整个集合 return collapseSingleResultList(multipleResults);
}
上述代码有两个比较关键的地方:
- 1、ResultSetWrapper rsw = getFirstResultSet(stmt):包装ResultSetWrapper,里面包含着columnNames、classNames、jdbcTypes等其他关键属性。
- 2、handleResultSet(rsw, resultMap, multipleResults, null):处理结果集的详细过程,真正的处理结果集的地方;
下面分别对这两个步骤进行详细的介绍,先来看看ResultSetWrapper 是如何包装的,getFirstResultSet(stmt)里面主要还是调用了ResultSetWrapper的构造方法来创建一个ResultSetWrapper对象,所以我们直接看ResultSetWrapper的构造方法:
//结果集列名集合
private final List<String> columnNames = new ArrayList<>();
//结果集JavaType类型集合
private final List<String> classNames = new ArrayList<>();
//结果集JdbcType类型集合
private final List<JdbcType> jdbcTypes = new ArrayList<>();public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {super();this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();this.resultSet = rs;//获取元数据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));}
}
如下图,可以看到,执行完ResultSetWrapper构造方法后,几个重要的属性的值如下所示:
接下来看另外一个关键的地方:handleResultSet(rsw, resultMap, multipleResults, null):执行具体的结果集封装操作
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {try {if (parentMapping != null) {handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);} else {if (resultHandler == null) {//第一步:如果结果处理器为空,创建一个默认的DefaultResultHandler结果集处理器DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);//第二步:处理每一行的值handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);//第三步:将结果集添加到集合中multipleResults.add(defaultResultHandler.getResultList());} else {handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);}}} finally {// issue #228 (close resultsets)closeResultSet(rsw.getResultSet());}
}
继续跟踪方法调用,接下来看一个handleRowValues()方法的源码看下如何处理每一行的值的:
//org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValues
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {//判断是否有嵌套结果集if (resultMap.hasNestedResultMaps()) {ensureNoRowBounds();checkResultHandler();//处理含有嵌套ResultMap的结果handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);} else {//此处示例我们没有嵌套结果集,所以执行的是else逻辑 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);}
}
本示例我们没有使用到嵌套结果集,所以执行的是else非嵌套逻辑:
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)throws SQLException {DefaultResultContext<Object> resultContext = new DefaultResultContext<>();//获取ResultSetResultSet resultSet = rsw.getResultSet();skipRows(resultSet, rowBounds);//主要判断上下文是否已关闭、resultSet是否关闭以及结果集是否还有元素while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);//获取行数据Object rowValue = getRowValue(rsw, discriminatedResultMap, null);storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}
}
直接看Mybatis是如何获取一行数据的:
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {final ResultLoaderMap lazyLoader = new ResultLoaderMap();//第一步:通过反射获取到需要封装的结果集实体类的构造方法,然后调用constructor.newInstance()创建一个对象//此时返回的rowValue: User{id='null', username='null'} 里面的属性都是nullObject rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);//行值不为空,并且结果对象有类型处理器if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {//第二步:创建元数据对象MetaObject,方便后面直接设置属性的值//将行值包装成元数据对象MetaObjectfinal MetaObject metaObject = configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;if (shouldApplyAutomaticMappings(resultMap, false)) {//第三步:自动映射查询出来的数据到前面创建好的Java对象属性中foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}return rowValue;
}
跟踪applyAutomaticMappings,看一下Mybatis是如何自动映射查询出来的数据到前面创建好的Java对象属性中的:
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {//第一步:建立好数据库列名和实体类属性名的映射关系List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);boolean foundValues = false;if (!autoMapping.isEmpty()) {for (UnMappedColumnAutoMapping mapping : autoMapping) {//第二步:根据mapping.column数据库列名,从查询结果集中获取到具体某一列的值//底层实际上就是调用的TypeHandler的:rs.getString("id")、rs.getString("name")//感兴趣的小伙伴可以查看这个方法:org.apache.ibatis.type.StringTypeHandler#getNullableResult(java.sql.ResultSet, java.lang.String)final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);if (value != null) {foundValues = true;}if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {// gcode issue #377, call setter on nulls (value is not 'found')//第三步:拿到值之后,那就需要动态设置属性的值为刚刚获取到的值//通过metaObject元数据对象直接修改属性的值// 给对象的属性设置值metaObject.setValue(mapping.property, value);}}}return foundValues;
}
简单总结一下applyAutomaticMappings()方法,最主要的逻辑就在这里:
- 第一步:建立好数据库列名和实体类属性名的映射关系
如下图,Mybatis建立好的数据库列名和实体类属性名的映射关系:
- 第二步:根据mapping.column数据库列名,从查询结果集中获取到具体某一列的值
- 第三步:拿到值之后,那就需要动态设置属性的值为刚刚获取到的值
如下图,可以看到,执行完第三步的时候,此时的结果集是下面这样的:
到这里,Mybatis查询结果集封装的步骤大体就完成了,接下来就是一级一级返回,添加到List结果集集合中,判断是返回一条数据还是直接返回整个结果集的集合。本文主要总结的是没有嵌套结果集的场景,感兴趣的小伙伴可以去看下嵌套结果集的处理流程,大体其实都是类似的,使用到了列名->属性映射关系,通过反射创建对象,拿到set方法,通过metaObject设置属性,无非可能会有一些集合的实例化等操作。
三、查询结果集封装流程
还是以一张流程图来总结一下查询结果集封装的过程:
四、总结
本篇文章详细总结了Mybatis查询结果集封装的整个流程,包括怎么建立数据库列名和实体类属性之间的映射、反射创建ResultType实体类对象、以及如何从结果集中拿到查询值,动态通过metaObject设置到返回类型实体类属性中等等。小伙伴们下去还是需要一步一步Debug调试一下,观察各个数据的流向和处理逻辑,这样效果可能会翻倍。
鉴于笔者水平有限,如果文章有什么错误或者需要补充的,希望小伙伴们指出来,希望这篇文章对大家有帮助。
MyBatis源码阅读(七) --- 查询结果集封装流程相关推荐
- mybatis源码阅读(七) ---ResultSetHandler了解一下
转载自 mybatis源码阅读(七) ---ResultSetHandler了解一下 1.MetaObject MetaObject用于反射创建对象.反射从对象中获取属性值.反射给对象设置属性值,参 ...
- mybatis源码阅读(八) ---Interceptor了解一下
转载自 mybatis源码阅读(八) ---Interceptor了解一下 1 Intercetor MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis允许 ...
- mybatis源码阅读(一):SqlSession和SqlSessionFactory
转载自 mybatis源码阅读(一):SqlSession和SqlSessionFactory 一.接口定义 听名字就知道这里使用了工厂方法模式,SqlSessionFactory负责创建SqlSe ...
- Mybatis源码阅读(一):Mybatis初始化1.1 解析properties、settings
*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...
- mybatis源码阅读(五) ---执行器Executor
转载自 mybatis源码阅读(五) ---执行器Executor 1. Executor接口设计与类结构图 public interface Executor {ResultHandler NO_ ...
- mybatis源码阅读(三):mybatis初始化(下)mapper解析
转载自 mybatis源码阅读(三):mybatis初始化(下)mapper解析 MyBatis 的真正强大在于它的映射语句,也是它的魔力所在.由于它的异常强大,映射器的 XML 文件就显得相对简单. ...
- Mybatis源码阅读之二——模板方法模式与Executor
[系列目录] Mybatis源码阅读之一--工厂模式与SqlSessionFactory 文章目录 一. 模板方法模式 二. 同步回调与匿名函数 三. Executor BaseExecutor与其子 ...
- mybatis源码阅读(六) ---StatementHandler了解一下
转载自 mybatis源码阅读(六) ---StatementHandler了解一下 StatementHandler类结构图与接口设计 BaseStatementHandler:一个抽象类,只是实 ...
- mybatis源码阅读(四):mapper(dao)实例化
转载自 mybatis源码阅读(四):mapper(dao)实例化 在开始分析之前,先来了解一下这个模块中的核心组件之间的关系,如图: 1.MapperRegistry&MapperPro ...
- mybatis源码阅读(二):mybatis初始化上
转载自 mybatis源码阅读(二):mybatis初始化上 1.初始化入口 //Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与 ...
最新文章
- 远程办公在美国有多受欢迎?有人宁愿降薪 50% 也不愿再回办公室
- Apache Kafka-初体验Kafka(03)-Centos7下搭建kafka集群
- HTTP和HTTPS总结
- 启动spark集群,JAVA_HOME is not set
- 全球10亿美元以上富翁人数排名,杭州超越东京,大家怎么看
- Python-类与文件读取结合
- CI/CD(持续集成构建/持续交付):如何测试/集成/交付项目代码?(Jenkins,TravisCI)
- python字符串format和center居中应用(三分钟读懂)
- 5G 登上“神坛”,区块链裁员求生!
- [Python] L1-035. 情人节 团体程序设计天梯赛GPLT
- Android应用模块之间通信模式
- python字符串与列表与运算_what's the python之基本运算符及字符串、列表、元祖、集合、字典的内置方法...
- 微信小程序官方开发文档
- Java字符串中数据排序
- #入坑keychron# 简单说说K3的使用体验
- 重庆邮电大学801信号与系统考研最核心知识点
- 阿拉伯数字转中国人民币大写字符串
- 【odoo15】自定义一个kanban视图
- 桌面计算机没有了 怎么恢复,告诉你电脑桌面图标都没了怎么恢复
- 2018总结-股权众筹和P2P网贷
热门文章
- java访问网络接口_Java网络访问 java调用http java调用其他接口
- OpenCV : 图像数字化
- 算法导论的道与术、工程师思维奠定能走多远
- android 表情的正则,Android 手势 正则匹配图片实例代码
- 373.查找和最小的K对数字
- K-Means优缺点
- UnicodeDecodeError: 'utf-8' codec can't decode byte 0xcd in position 0: invalid continuation byte
- 第三章CDMA的原理和应用(3)
- 2020 比特大陆 面经
- 代码整洁之道读书笔记----第四章---注释--第二节-坏孩子与坏注释