# MyBatis(技术NeiMu):核心处理层(ResultSetHandler)

回顾

前面我们已经了解了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可以进行简单映射和复杂的嵌套映射

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值