我们之前介绍过了MyBatis 四大核心配置之 Executor、StatementHandler、 ParameterHandler,今天本文的主题是介绍一下 MyBatis 最后一个神器也就是 ResultSetHandler。那么开始我们的讨论

ResultSetHandler 简介

回想一下,一条 SQL 的请求过程会经过哪几个步骤? 首先会经过 Executor 执行器,它主要负责管理创建 StatementHandler 对象,然后由 StatementHandler 对象做数据库的连接以及生成 Statement 对象,并解析 SQL 参数,由 ParameterHandler 对象负责把 Mapper 方法中的参数映射到 XML 中的 SQL 语句中,那么是不是还少了一个步骤,就能完成一个完整的 SQL 请求了?没错,这最后一步就是 SQL 结果集的处理工作,也就是 ResultSetHandler 的主要工作

要了解 ResultSetHandler 之前,首先需要了解 ResultSetHandler的继承关系以及基本方法

  1. public interface ResultSetHandler {
  2. // 处理结果集
  3. <E> List<E> handleResultSets(Statement stmt) throws SQLException;
  4. // 批量处理结果集
  5. <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
  6. // 处理存储过程的结果集
  7. void handleOutputParameters(CallableStatement cs) throws SQLException;
  8. }

ResultSetHandler是一个接口,它只有一个默认的实现类,像是 ParameterHandler 一样,它的默认实现类是DefaultResultSetHandler

ResultSetHandler 创建

ResultSetHandler 是在处理查询请求的时候由 Configuration 对象负责创建,示例如下

  1. protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  2. this.configuration = mappedStatement.getConfiguration();
  3. this.executor = executor;
  4. this.mappedStatement = mappedStatement;
  5. this.rowBounds = rowBounds;
  6. this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  7. this.objectFactory = configuration.getObjectFactory();
  8. if (boundSql == null) { // issue #435, get the key before calculating the statement
  9. generateKeys(parameterObject);
  10. boundSql = mappedStatement.getBoundSql(parameterObject);
  11. }
  12. this.boundSql = boundSql;
  13. // 创建参数处理器
  14. this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  15. // 创建结果映射器
  16. this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  17. }
  18. public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
  19. ResultHandler resultHandler, BoundSql boundSql) {
  20. // 由 DefaultResultSetHandler 进行初始化
  21. ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
  22. resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  23. return resultSetHandler;
  24. }

上述的创建过程是对 ResultSetHandler 创建过程以及初始化的简单解释,下面是对具体的查询请求进行分析

ResultSetHandler 处理结果映射

回想一下,我们在进行传统crud操作的时候,哪些方法是需要返回值的?当然我们说的返回值指的是从数据库中查询出来的值,而不是标识符,应该只有查询方法吧?所以 MyBatis 只针对 query 方法做了返回值的映射,代码如下:

PreparedStatementHandler.java

  1. @Override
  2. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  3. PreparedStatement ps = (PreparedStatement) statement;
  4. ps.execute();
  5. // 处理结果集
  6. return resultSetHandler.<E> handleResultSets(ps);
  7. }
  8. @Override
  9. public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
  10. PreparedStatement ps = (PreparedStatement) statement;
  11. ps.execute();
  12. // 批量处理结果集
  13. return resultSetHandler.<E> handleCursorResultSets(ps);
  14. }

CallableStatementHandler.java 处理存储过程的SQL

  1. @Override
  2. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  3. CallableStatement cs = (CallableStatement) statement;
  4. cs.execute();
  5. List<E> resultList = resultSetHandler.<E>handleResultSets(cs);
  6. resultSetHandler.handleOutputParameters(cs);
  7. return resultList;
  8. }
  9. @Override
  10. public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
  11. CallableStatement cs = (CallableStatement) statement;
  12. cs.execute();
  13. Cursor<E> resultList = resultSetHandler.<E>handleCursorResultSets(cs);
  14. resultSetHandler.handleOutputParameters(cs);
  15. return resultList;
  16. }

DefaultResultSetHandler 源码解析

MyBatis 只有一个默认的实现类就是 DefaultResultSetHandler,ResultSetHandler 主要负责处理两件事

  1. 处理 Statement 执行后产生的结果集,生成结果列表
  2. 处理存储过程执行后的输出参数

按照 Mapper 文件中配置的 ResultType 或 ResultMap 来封装成对应的对象,最后将封装的对象返回即可。

来看一下主要的源码:

  1. @Override
  2. public List<Object> handleResultSets(Statement stmt) throws SQLException {
  3. ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
  4. final List<Object> multipleResults = new ArrayList<Object>();
  5. int resultSetCount = 0;
  6. // 获取第一个结果集
  7. ResultSetWrapper rsw = getFirstResultSet(stmt);
  8. // 获取结果映射
  9. List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  10. // 结果映射的大小
  11. int resultMapCount = resultMaps.size();
  12. // 校验结果映射的数量
  13. validateResultMapsCount(rsw, resultMapCount);
  14. // 如果ResultSet 包装器不是null, 并且 resultmap 的数量 > resultSet 的数量的话
  15. // 因为 resultSetCount 第一次肯定是0,所以直接判断 ResultSetWrapper 是否为 0 即可
  16. while (rsw != null && resultMapCount > resultSetCount) {
  17. // 从 resultMap 中取出 resultSet 数量
  18. ResultMap resultMap = resultMaps.get(resultSetCount);
  19. // 处理结果集, 关闭结果集
  20. handleResultSet(rsw, resultMap, multipleResults, null);
  21. rsw = getNextResultSet(stmt);
  22. cleanUpAfterHandlingResultSet();
  23. resultSetCount++;
  24. }
  25. // 从 mappedStatement 取出结果集
  26. String[] resultSets = mappedStatement.getResultSets();
  27. if (resultSets != null) {
  28. while (rsw != null && resultSetCount < resultSets.length) {
  29. ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
  30. if (parentMapping != null) {
  31. String nestedResultMapId = parentMapping.getNestedResultMapId();
  32. ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
  33. handleResultSet(rsw, resultMap, null, parentMapping);
  34. }
  35. rsw = getNextResultSet(stmt);
  36. cleanUpAfterHandlingResultSet();
  37. resultSetCount++;
  38. }
  39. }
  40. return collapseSingleResultList(multipleResults);
  41. }

其中涉及的主要对象有:

ResultSetWrapper : 结果集的包装器,主要针对结果集进行的一层包装,它的主要属性有

  • ResultSet : Java JDBC ResultSet接口表示数据库查询的结果。 有关查询的文本显示了如何将查询结果作为java.sql.ResultSet返回。 然后迭代此ResultSet以检查结果。
  • TypeHandlerRegistry: 类型注册器,TypeHandlerRegistry 在初始化的时候会把所有的 Java类型和类型转换器进行注册。
  • ColumnNames: 字段的名称,也就是查询操作需要返回的字段名称
  • ClassNames: 字段的类型名称,也就是 ColumnNames 每个字段名称的类型
  • JdbcTypes: JDBC 的类型,也就是java.sql.Types 类型

ResultMap: 负责处理更复杂的映射关系

multipleResults:

其中的主要方法是 handleResultSet

  1. private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  2. try {
  3. if (parentMapping != null) {
  4. // 处理多行结果的值
  5. handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
  6. } else {
  7. if (resultHandler == null) {
  8. DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
  9. handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
  10. multipleResults.add(defaultResultHandler.getResultList());
  11. } else {
  12. handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
  13. }
  14. }
  15. } finally {
  16. // issue #228 (close resultsets)
  17. closeResultSet(rsw.getResultSet());
  18. }
  19. }
  20. // 如果有嵌套的ResultMap 的话
  21. // 确保没有行绑定
  22. // 检查结果处理器
  23. // 如果没有的话,直接处理简单的ResultMap
  24. public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  25. if (resultMap.hasNestedResultMaps()) {
  26. ensureNoRowBounds();
  27. checkResultHandler();
  28. handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  29. } else {
  30. handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  31. }
  32. }

handleResultSets 方法返回的是 collapseSingleResultList(multipleResults) ,它是什么呢?

  1. private List<Object> collapseSingleResultList(List<Object> multipleResults) {
  2. return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
  3. }

它是判断的 multipleResults 的数量,如果数量是 1 ,就直接取位置为0的元素,如果不是1,那就返回 multipleResults 的真实数量

那么 multipleResults 的数量是哪来的呢?

它的值其实是处理结果集中传递进去的

  1. handleResultSet(rsw, resultMap, multipleResults, null);

然后在处理结果集的方法中对 multipleResults 进行添加

  1. multipleResults.add(defaultResultHandler.getResultList());

下面我们来看一下返回的真实实现类 DefaultResultSetHandler 中的结构组成

在 DefaultResultSetHandler 中处理完结果映射,并把上述结构返回给调用的客户端,从而执行完成一条完整的SQL语句。

我的公众号二维码:欢迎关注

文章参考:

https://blog.csdn.net/qq924862077/article/details/52704191

MyBatis 核心配置综述之 ResultSetHandler的更多相关文章

  1. MyBatis 核心配置综述之StatementHandler

    目录 MyBatis 核心配置综述之StatementHandler MyBatis 四大组件之StatementHandler StatementHandler 的基本构成 StatementHan ...

  2. MyBatis 核心配置综述之 ParameterHandler

    目录 ParameterHandler 简介 ParameterHandler 创建 ParameterHandler 中的参数从何而来 ParameterHandler 解析 MyBatis 四大核 ...

  3. MyBatis 核心配置综述之Executor

    目录 MyBatis四大组件之 Executor执行器 Executor的继承结构 Executor创建过程以及源码分析 Executor接口的主要方法 Executor 的现实抽象 上一篇我们对Sq ...

  4. 1.2(Mybatis学习笔记)Mybatis核心配置

    一.Mybatis核心对象 1.1SqlSeesionFactory SqlSessionFactory主要作用是创建时SqlSession. SqlSessionFactory可通过SqlSessi ...

  5. Mybatis学习——Mybatis核心配置

    MyBatis的核心配置 在使用MyBatis框架时,设计两个核心的d对象:SqlSessionFactory和SqlSession. SqlsessionFactory SqlSessionFact ...

  6. Mybatis的核心配置

    之前了解了Mybatis的基本用法,现在学习一下Mybatis框架中的核心对象以及映射文件和配置文件,来深入的了解这个框架. 1.Mybatis的核心对象 使用MyBatis框架时,主要涉及两个核心对 ...

  7. 笔记:MyBatis XML配置详解

    MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息.文档的顶层结构如下: configuration 配置 properties ...

  8. MyBatis核心配置文件详解

    ------------------------siwuxie095                                     MyBatis 核心配置文件详解         1.核心 ...

  9. Java-MyBatis:MyBatis 3 配置

    ylbtech-Java-MyBatis:MyBatis 3 配置 1.返回顶部 1. XML 映射配置文件 MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置(settings) ...

随机推荐

  1. CSS3过渡与动画

    一.CSS3 过渡 transition-property 规定过渡效果的 CSS 属性名 -webkit-transition-property: none / all / property; -m ...

  2. 使用Jenkins部署.Net Core遇到的几个坑

    搞过CI/CD的同学一定吃过不少苦头,或者说遇到不少坑,但是对自动化的执着住挡不了前进的步伐,如果你缺少了运维这一块知识,那么你的流水线总是不那么完美,本文记录的是自己躺过的坑,希望对你有所帮助. 一 ...

  3. JVM中ClassLoader的学习

    JVM中class loaderの学习 一..class文件和jvm的关系 类的加载 所有的编译生成的.class文件都会被直接加载到JVM里面来吗(并不 首先我们明确一个概念,.class文件加载到 ...

  4. 浅谈c++中的KMP

    百度上一些关于KMP算法的一些基本介绍 所谓KMP,其实就是一种经过改进的模式串匹配算法(即在原串A中查找是否存在模式串B) 通常情况下,我们是这样匹配的 串A    X Y Z X X Y Z X  ...

  5. ZooKeeper入门(二) Zookeeper选举

    1 背景 1.1 什么是leader选举 在zookeeper集群中,每个节点都会投票,如果某个节点获得超过半数以上的节点的投票,则该节点就是leader节点了 1.2 zookeeper集群选举le ...

  6. HDU 2121:Ice_cream’s world II(不定根的最小树形图)

    题目链接 题意 求有向图的最小生成树,且根不定. 思路 最小树形图即求有向图的最小生成树,用的是朱刘算法. 这里不定根,那么可以建立一个虚根,让虚根和所有点相连,权值为一个很大的数(这里直接设为所有边 ...

  7. 网络下载器 迅雷大众版 v7.9.42.5050 精简绿色版

    下载地址:点我 基本介绍 迅雷大众版是一款采用了先进的超线程技术基于网格原理,能够将存在于第三方服务器和计算机上的数据文件进行有效整合,通过这种先进的超线程技术,用户能够以更快的速度从这些第三方节点平 ...

  8. Unix及Linux编辑器vi/vim基本使用方法

  9. 聊聊C语言的预编译指令include

    "include"相信大家不会陌生,在我们写代码时,开头总会来一句"include XXX".include是干嘛用的,很多教材都提到了,因此这里不会再详细解释 ...

  10. SpringBoot2.x 整合Spring-Session实现Session共享

    SpringBoot2.x 整合Spring-Session实现Session共享 1.前言 发展至今,已经很少还存在单服务的应用架构,不说都使用分布式架构部署, 至少也是多点高可用服务.在多个服务器 ...