# MyBatis(技术NeiMu):核心处理层(ResultSetHandler)_mybatis resultsethandler-程序员宅基地

技术标签: java  MyBatis  数据库  sql  

回顾

前面我们已经了解了MyBatis的整个初始化过程,与SQL节点的解析与SQL节点的SQL是如何与实参进行绑定起来、如何根据实参进行动态拼接,下面来看一下MyBatis是如何处理结果集的

ResultSetHandler

MyBatis会根据SQL映射配置文件中定义的映射规则,比如resultMap标签、resultType属性来进行结果集映射,映射成相应的结果对象,而结果集映射机制是MyBatis的核心机制之一

当StatementHandler接口在执行完指定的select语句时候,就会调用ResultSetHandler来处理结果集,ResultSetHandler就是负责完成映射处理的,这里我们先不用知道StatementHandler如何执行SQL,我们只认识了DynamicSqlSource和RawSqlSource来存储SQL节点中完成解析的SQL而已
在这里插入图片描述
该接口提供三种方法,处理不同的结果集

  • handlerResultSets:处理结果集,生成相应的结果对象集合
  • handlerCursorResults:处理返回的游标对象
  • handlerOutputParameters:处理存储过程的输出参数的

DefaultResultSetHandler

MyBatis仅仅只有一个DefaultResultSetHandler实现了ResultSetHandler接口
在这里插入图片描述
下面来介绍一下其比较重要的成员属性

  • executor:处理器,用来执行SQL的
  • configuration:MyBatis的上下文配置
  • RawBounds:用于分页的
  • mappedStatement:存储SQL节点的
  • resultHandler:指定用于处理结果集的ResultHandler对象,ResultSetHandler其实是一个组合对象而已,真实处理是交由ResultHandler来进行
  • TypeHandlerRegistry:类型转换注册中心
  • ObjectFactory:用于实例对象工厂的
  • ReflectorFactory:反射工厂,用于获取类的Reflector,根据Reflector可以创建对象出来

handlerResultSets方法

处理结果集映射的是handlerResultSets方法,该方法不仅可以处理普通的SQL语句(Statement)、也能处理预编译SQL(PrepareStatement),还有存储过程(CallableStatement)

源码如下

public List<Object> handleResultSets(Statement stmt) throws SQLException {
    
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
	//使用List来存储所有的映射结果集得到的结果对象,因为支持存储过程,所以可能会存在多结果集!
    //因此使用List来接收
    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    //获取第一个ResultSet对象
    ResultSetWrapper rsw = getFirstResultSet(stmt);
	//通过MappedStatement获得ResultMaps对象,也就是对应的resultMap标签
    //一个MappedStatement可能会拥有多个ResultMap对象
    //但我们一般只会给SQL节点的resultMap属性设置一个id,其实可以使用逗号来分割使用多个ResultMap的
    //当然这是要对应存在多结果集的时候才需要使用,也就是执行多条查询SQL,返回多个结果集
    //每个结果集会有对应的ResultMap来进行映射
    //前面在初始化MyBatis的时候就已经了解过了
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    //对resultMaps进行校验
    validateResultMapsCount(rsw, resultMapCount);
    //判空,如果结果集不为空,那么对应的ResultMap对象不能为空
    while (rsw != null && resultMapCount > resultSetCount) {
    
        //获取对应的ResultMap
      ResultMap resultMap = resultMaps.get(resultSetCount);
        //使用ResultMap去处理结果集
      handleResultSet(rsw, resultMap, multipleResults, null);
        //获取下一个结果集
      rsw = getNextResultSet(stmt);
        //清除操作
      cleanUpAfterHandlingResultSet();
        //迭代resultSetCount,用于获取下一个结果集处理
      resultSetCount++;
    }
	
    //处理嵌套映射的,也就是要给对象中的对象成员属性进行映射
    //也就是SQL节点中的resultSets属性
    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++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

整个的处理过程如下

  • 获取第一个结果集,也就是第一个ResultSet

  • 获取前面初始化SQL节点时得到的ResultMap集合

  • 判断第一个结果集是否为空,如果为空,允许ResultMap集合也为空,如果存在结果集,那么对应的ResultMap集合也不能为空,这也是为什么要去获取第一个ResultSet的缘故

  • 然后去迭代ResultSet和ResultMap,使用对应的ResultMap来处理结果集,一个结果集对应一个ResultMap对象,也就是说ResultMap的对象必须要大于等于结果集数量

  • 其实仅仅只是像迭代器一样,遍历ResultSet,然后使用对应的ResultMap来处理而已,而迭代器其实就是Statement本身,通过getNextResults来进行获取下一个结果集

  • 对于每一个结果集如果不为空,则会封装成ResultSetWrapper

  • 最后进行处理嵌套映射(一般不会使用嵌套映射)

ResultSet的迭代过程

如何获取第一个ResultSet结果集?

获取第一个ResultSet结果集,其实可以说是获取它的一个ResultSet迭代器,也就是ResultSetWrapper对象

private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    
    //调用Statement去获取结果集
    ResultSet rs = stmt.getResultSet();
    //对空进行循环,这是针对HSQLDB2.1版本的页特殊处理
    //因为HSQLDB2.1第一次并不会返回结果集
    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 {
    
          //break
        if (stmt.getUpdateCount() == -1) {
    
          // no more results. Must be no resultset
          break;
        }
      }
    }
    //如果存在结果集,就封装ResultSetWrapepr返回,否则返回Null
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
  }

可以看到,获取第一个结果集的方式其实就是判断遍历整个结果集,获取第一个非空的结果集,如果不存在,则会返回Null,如果存在,封装成ResultWrapper对象返回

获取第一个非空的结果集,这是为了支持HSQLDB2.1第一次通过Statement获取ResultSet可能为空

如何获取下一个结果集?

对应的方法为getNextResultSet(stmt)

private ResultSetWrapper getNextResultSet(Statement stmt) {
    
    // Making this method tolerant of bad JDBC drivers
    try {
    
      if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
    
        // Crazy Standard JDBC way of determining if there are more results
        if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
    
            //通过Statement继续获取ResultSet
          ResultSet rs = stmt.getResultSet();
          if (rs == null) {
    
            return getNextResultSet(stmt);
          } else {
    
            return new ResultSetWrapper(rs, configuration);
          }
        }
      }
    } catch (Exception e) {
    
      // Intentionally ignored.
    }
    return null;
  }

可以看到,其实就是继续通过Statement继续去获取下一个非空的ResultSet,然后封装成ResultSetWrapper对象返回

其实ResultSet的迭代过程几乎是交由Statement去完成的

ResultSetWrapper

前面已经提到过,对于ResultSet结果集,MyBatis并不是直接使用,而是封装成一个ResultSetWrapper,下面就看看这个ResultSetWrapper是处理什么的
在这里插入图片描述
成员属性

  • resultSet:底层的结果集对象
  • TypeHandlerRegistry:类型转换注册中心
  • columnNames:结果集中的每一列的列名
  • classNames:每一列对应的java类型
  • jdbcTypes:每一列的jdbc类型
  • typeHandlerMap:每列对应的TypeHandler
  • mappedColumnNamesMap:记录被映射的列名,value为被映射的列名,而key为resultMap的ID
  • unMappedColumnNamesMap:记录未被映射的列名,value为未被映射的列名,而key为resultMap的ID

说白了,ResultSetMapper其实封装ResultSet结果集中进行列映射的信息,比如使用到的TypeHandler、列的jdbcType和对应的javaType,还有不进行映射的列(SQL查出来了,但ResultMap没有进行对应的配置来映射该列)、进行映射的列,可以通过ResultSetWrapper来获取到结果集映射的一系列信息,比如要映射的列名、不需要映射的列名、列对应的java类型、列对应的jdbc类型、列需要使用到的TypeHandler

构造方法

上面的一系列集合,都是ResultSetWrapper通过哦构造方法来进行初始化的

public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    
    super();
    //从Configuration中取出TypeHandlerRegistry
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;
    //通过结果集获取MetaData(JDBC的ResultSetMetaData,可以根据其获取结果集的信息)
    final ResultSetMetaData metaData = rs.getMetaData();
    final int columnCount = metaData.getColumnCount();
    //遍历列
    for (int i = 1; i <= columnCount; i++) {
    
        //获取所有的列名、这里可能是别名
        //封装在columnNames中
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
        //并且获取列对应的JdbcType,然后封装在jdbcTypes集合中
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
        //
      classNames.add(metaData.getColumnClassName(i));
    }
  }

下面来看是如何获取TypeHandler的,前面我们已经看到了,TypeHandler是用来做类型转换的

public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
    
    TypeHandler<?> handler = null;
    //从typeHandlerMap中获取TypeHandler
    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) {
    
      JdbcType jdbcType = getJdbcType(columnName);
      handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
      // Replicate logic of UnknownTypeHandler#resolveTypeHandler
      // See issue #59 comment 10
      if (handler == null || handler instanceof UnknownTypeHandler) {
    
        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) {
    
        handler = new ObjectTypeHandler();
      }
      columnHandlers.put(propertyType, handler);
    }
    return handler;
  }
映射

前面已经提到了,对于每一个ResultSet会封装成一个ResultSetWrapper,里面可以获取映射的一系列信息,然后DefaultResultSetHandler的handlerResultSet可以进行简单映射和复杂的嵌套映射

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/GDUT_Trim/article/details/121893677

智能推荐

CheckableLinearLayout 实现一个可选中的layout 以及 CheckableImageView_linearlayout setchecked-程序员宅基地

文章浏览阅读2.3k次。package com.example.testchecklinearlayout;import android.content.Context;import android.util.AttributeSet;import android.widget.Checkable;import android.widget.LinearLayout;public class Checkab_linearlayout setchecked

git---全局设置用户名、密码、邮箱_git global username-程序员宅基地

文章浏览阅读1w次,点赞4次,收藏16次。git—全局设置用户名、密码、邮箱git config命令的–global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。1.查看git配置信息$ git config --list2.查看git用户名、密码、邮箱的配置$ git config user.name$ git config user.password$ git config user.email3.设置git用户名、密码、邮箱的配置$ git con_git global username

WakeOnLan+RemoteDesktop_wake on lan and remote desktop-程序员宅基地

文章浏览阅读713次。这两天就要开学了,之前的几个学期我都是把台式机搬到学校去但是这学期格外的懒,于是就想到了利用网络唤醒加上远程桌面远程访问家里的台式机WakeOnLan查看主板是否支持wol(只要不是太老的主板都支持)进入BIOS,开启wol 这个选项的主板不一样叫法不同,根据主板类型上网查一下就好,我的主板是b75,选项叫PME唤醒进入系统,打开设备管理器,选择你的网卡,点击属性最后一栏,至少勾选前两项.然后将..._wake on lan and remote desktop

Mac OS X 10.4作为客户端挂载SMB时显示0字节可用问题解决-程序员宅基地

文章浏览阅读531次。最近遇到一件怪事,RHEL运行的samba,用Mac OS X 10.4去访问,无论怎样都显示可用空间为0字节,可以创建目录,无法写入文件。文件服务器实际上有nTB可用。为解决问题,google上搜了搜,下面是两个相关的讨论:http://discussions.apple.com/thread.jspa?messageID=5540225http://discussion..._smb共享全部0字节

-XX:+TraceClassLoading参数可以跟踪显示类加载机制-程序员宅基地

文章浏览阅读3.8k次。-XX:+TraceClassLoading_-xx:+traceclassloading

B&O蓝牙耳机E8的连接_bo e8蓝牙连接-程序员宅基地

文章浏览阅读10w+次。 1、从充电盒中取出耳机,然后点击右耳机将其打开。产品指示灯变为白色,可听到声音,这时产品就可以使用了。2、使耳机之间保持小于20cm的距离,触摸并按住2只耳机5秒钟以启动蓝牙配对。指示灯开始闪烁蓝色,并听到声音提示。打开设备上的蓝牙设置,然后选择 Beoplay E83、按键操作如下:播放/暂停 右耳机按1下 下一曲 右耳机按2下 上一曲 ..._bo e8蓝牙连接

随便推点

基于多线程的Linux聊天室系统设计(C语言实现)_linux下多线程图形化界面聊天软件设计-程序员宅基地

文章浏览阅读1.3k次,点赞3次,收藏18次。LTS聊天室文件夹:包含客户端服务器源代码以及完成好的大作业服务器端代码客户端代码_linux下多线程图形化界面聊天软件设计

WinDbg命令详解--进程_windbg 调试怎么查看进程-程序员宅基地

文章浏览阅读2.4k次。指令列表.tlist 查看进程简要信息:进程号和进程名称.tlist 查看进程详细信息:进程号和进程名称,命令行参数,SessionID,用户等信息!teb 线程块环境信息!peb 进程块环境信息lm 查看模块的简要信息lm -v 查看模块的相信信息!handle 查看句柄表信息!handle id 查看特定的句柄信息_windbg 调试怎么查看进程

0x8(0x80070035找不到网络路径)-程序员宅基地

文章浏览阅读242次。c语言编程 main()  {unsignedchara,b,ca为16进制化为二进制为0011,b为0011|1000=1011=11,c为b左移一位为10110相当于b*2=22请教大家一个计算机2级的题目main(){unsignedcha"|"这个是按位或C没有初始化不能移位操作,安装程序..._找不到网络名称0x8

两个暴牛的js加密函数 求解码办法-程序员宅基地

文章浏览阅读630次。eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[...

基于Python爬虫黑龙江哈尔滨天气预报数据可视化系统设计与实现(Django框架) 研究背景与意义、国内外研究现状-程序员宅基地

文章浏览阅读2.5k次,点赞15次,收藏24次。基于Python爬虫黑龙江哈尔滨天气预报数据可视化系统设计与实现(Django框架) 研究背景与意义、国内外研究现状,帮助用户更好地理解和利用天气数据。而基于Python爬虫的天气预报数据可视化系统,能够利用Python强大的数据分析和可视化库,提供更加灵活、自定义的天气预报数据查询和展示功能。另外,一些研究者也将天气数据与其他数据进行融合,实现多维度的可视化分析,如将天气数据与交通数据结合,帮助用户了解天气对交通流量的影响。同时,这些网站通常有着繁琐的页面布局和冗长的加载时间,用户体验较差。

Windows操作系统下IIS版本对应关系_2003 iis7.5-程序员宅基地

文章浏览阅读6.9k次。各个系统对应的iis版本不同:Windows 2000 Server→IIS5.0Windows XP SP1→IIS5.0 Windows XP SP2,SP3→IIS5.1 Windows Server 2003→IIS6.0Windows Vista Ultimate→IIS7.0Windows 7→iis7, iis7.5Windows server2008_2003 iis7.5

推荐文章

热门文章

相关标签