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源码阅读(七) --- 查询结果集封装流程相关推荐

  1. mybatis源码阅读(七) ---ResultSetHandler了解一下

    转载自  mybatis源码阅读(七) ---ResultSetHandler了解一下 1.MetaObject MetaObject用于反射创建对象.反射从对象中获取属性值.反射给对象设置属性值,参 ...

  2. mybatis源码阅读(八) ---Interceptor了解一下

    转载自  mybatis源码阅读(八) ---Interceptor了解一下 1 Intercetor MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis允许 ...

  3. mybatis源码阅读(一):SqlSession和SqlSessionFactory

    转载自  mybatis源码阅读(一):SqlSession和SqlSessionFactory 一.接口定义 听名字就知道这里使用了工厂方法模式,SqlSessionFactory负责创建SqlSe ...

  4. Mybatis源码阅读(一):Mybatis初始化1.1 解析properties、settings

    *************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...

  5. mybatis源码阅读(五) ---执行器Executor

    转载自  mybatis源码阅读(五) ---执行器Executor 1. Executor接口设计与类结构图 public interface Executor {ResultHandler NO_ ...

  6. mybatis源码阅读(三):mybatis初始化(下)mapper解析

    转载自 mybatis源码阅读(三):mybatis初始化(下)mapper解析 MyBatis 的真正强大在于它的映射语句,也是它的魔力所在.由于它的异常强大,映射器的 XML 文件就显得相对简单. ...

  7. Mybatis源码阅读之二——模板方法模式与Executor

    [系列目录] Mybatis源码阅读之一--工厂模式与SqlSessionFactory 文章目录 一. 模板方法模式 二. 同步回调与匿名函数 三. Executor BaseExecutor与其子 ...

  8. mybatis源码阅读(六) ---StatementHandler了解一下

    转载自  mybatis源码阅读(六) ---StatementHandler了解一下 StatementHandler类结构图与接口设计 BaseStatementHandler:一个抽象类,只是实 ...

  9. mybatis源码阅读(四):mapper(dao)实例化

    转载自   mybatis源码阅读(四):mapper(dao)实例化 在开始分析之前,先来了解一下这个模块中的核心组件之间的关系,如图: 1.MapperRegistry&MapperPro ...

  10. mybatis源码阅读(二):mybatis初始化上

    转载自  mybatis源码阅读(二):mybatis初始化上 1.初始化入口 //Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与 ...

最新文章

  1. 远程办公在美国有多受欢迎?有人宁愿降薪 50% 也不愿再回办公室
  2. Apache Kafka-初体验Kafka(03)-Centos7下搭建kafka集群
  3. HTTP和HTTPS总结
  4. 启动spark集群,JAVA_HOME is not set
  5. 全球10亿美元以上富翁人数排名,杭州超越东京,大家怎么看
  6. Python-类与文件读取结合
  7. CI/CD(持续集成构建/持续交付):如何测试/集成/交付项目代码?(Jenkins,TravisCI)
  8. python字符串format和center居中应用(三分钟读懂)
  9. 5G 登上“神坛”,区块链裁员求生!
  10. [Python] L1-035. 情人节 团体程序设计天梯赛GPLT
  11. Android应用模块之间通信模式
  12. python字符串与列表与运算_what's the python之基本运算符及字符串、列表、元祖、集合、字典的内置方法...
  13. 微信小程序官方开发文档
  14. Java字符串中数据排序
  15. #入坑keychron# 简单说说K3的使用体验
  16. 重庆邮电大学801信号与系统考研最核心知识点
  17. 阿拉伯数字转中国人民币大写字符串
  18. 【odoo15】自定义一个kanban视图
  19. 桌面计算机没有了 怎么恢复,告诉你电脑桌面图标都没了怎么恢复
  20. 2018总结-股权众筹和P2P网贷

热门文章

  1. java访问网络接口_Java网络访问 java调用http java调用其他接口
  2. OpenCV : 图像数字化
  3. 算法导论的道与术、工程师思维奠定能走多远
  4. android 表情的正则,Android 手势 正则匹配图片实例代码
  5. 373.查找和最小的K对数字
  6. K-Means优缺点
  7. UnicodeDecodeError: 'utf-8' codec can't decode byte 0xcd in position 0: invalid continuation byte
  8. 第三章CDMA的原理和应用(3)
  9. 2020 比特大陆 面经
  10. 代码整洁之道读书笔记----第四章---注释--第二节-坏孩子与坏注释