19-Mybatis源码分析(四大对象-ResultSetHandler结果集映射)

四大对象-ResultSetHandler(结果集映射)

一、ResultSetHandler

  • ResultSetHandler在Mybatis四大对象中负责将数据库结果集转换为Java对象集合。它是一个接口,有且只有一个实现类DefaultResultSetHandler。
public interface ResultSetHandler {
    /**
     * 将ResultSet映射成对应Java类集合
     */
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;

    /**
     * 将ResultSet处理成Cursor对象
     */
    <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

    /**
     * 存储过程的处理
     */
    void handleOutputParameters(CallableStatement cs) throws SQLException;
}
  • handleResultSets是处理数据库结果集最核心的方法。

二、DefaultResultSetHandler

  • DefaultResultSetHandler是ResultSetHandler结果的唯一实现类,结果集的处理基本是都在ResultSetHandler里面。我们通过ResultSetHandler来看看结果集的处理流程,因为处理过程中的细节非常多,需要考虑各种配置,嵌套之类的,因此我们梳理流程旨在弄清楚整个主流程,不会过于解析每一行代码。

2.1 DefaultResultSetHandler#handleResultSets

  • handleResultSets方法将ResultSet结果集转换为对应的Java类对象集合,在在StatementHandler里面的query方法会调用该方法。
/**
   * 处理结果集的方法,在StatementHandler里面的query方法会调用该方法
   * PS:该方法可以处理Statement,PreparedStatement和存储过程的CallableStatement返回的结果集。而CallableStatement是支持返回多结
   * 果集的,普通查询一般是单个结果集
   * */
  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    //1.保存结果集对象。普通的查询,实际就一个ResultSet,也就是说,multipleResults最多就一个元素。在多ResultSet的
    //结果集合一般在存储过程出现,此时每个ResultSet对应一个Object对象,每个Object是 List<Object>对象。
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    //2.Statement可能返回多个结果集对象,先处理第一个结果集,封装成ResultSetWrapper对象
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    //3.将结果集转换需要知道转换规则,而记录和JavaBean的转换规则都在resultMap里面,这里获取resultMap,注意获取
    //的是全部的resultMap,是一个list,但是对于普通查询,resultMaps只有一个元素(普通查询一个响应只有一个ResultSet,存储过程可能有多个)
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    //4.结果集和转换规则均不能为空,否则抛异常
    validateResultMapsCount(rsw, resultMapCount);
    //5.一个一个处理(普通查询其实就处理一次)
    while (rsw != null && resultMapCount > resultSetCount) {
      //6.获取当前结果集对应的resultMap(注意前面是全部的resultMap,这里是获取到自己需要的,普通查询就是获取一个,一般也就只有一个)
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //7.根据规则(resultMap)处理 ResultSet,将结果集转换为Object列表,并保存到multipleResults
      //这是获取结果集的最核心的方法步骤
      handleResultSet(rsw, resultMap, multipleResults, null);
      //8.获取下一个结果集
      rsw = getNextResultSet(stmt);
      //9.清空nestedResultObjects对象
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    //10.获取多结果集。一般出现在存储过程中,不分析了...
    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++;
      }
    }
    //11.如果是multipleResults只有一个元素,则取首元素返回(collapseSingleResultList中会判断如果只有一个元素就获取并返回)
    return collapseSingleResultList(multipleResults);
  }
  • 核心流程
1.mappedStatement.getResultMaps(); //获取ResultMap
2.handleResultSet(rsw, resultMap, multipleResults, null);//根据规则(resultMap)处理 ResultSet,将结果集转换为Object列表
3.return collapseSingleResultList(multipleResults);//返回结果集

PS:后面重点解析第二步handleResultSet里面的细节。

2.2 DefaultResultSetHandler#handleResultSet

  • handleResultSet根据规则(resultMap)处理 ResultSet,将结果集转换为Object列表,并保存到multipleResults
/**
   * 根据规则(resultMap)处理 ResultSet,将结果集转换为Object列表,并保存到multipleResults
   *
   * */
  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
        //1.非存储过程的情况,parentMapping为null。handleResultSets方法的第一个while循环传参就是null
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        //1.2如果没有自定义的resultHandler,则使用默认的DefaultResultHandler对象
        if (resultHandler == null) {
          //1.3创建DefaultResultHandler对象
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          //1.4(核心方法)处理ResultSet返回的每一行Row,里面会循环处理全部的结果集
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          //1.5将defaultResultHandler的处理结果,添加到multipleResults中
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          //1.6 和1.4一样,处理每一行,里面会循环处理全部的结果集
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      //关闭ResultSet对象
      closeResultSet(rsw.getResultSet());
    }
  }
  • 核心流程
1.handleRowValues //处理结果集 
2.multipleResults.add //添加结果集到对应集合
PS:这一步主要是做一些逻辑的判断,主要处理是在handleRowValues方法里面

2.3 DefaultResultSetHandler#handleRowValues

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
      //1.处理嵌套映射的情况
    if (resultMap.hasNestedResultMaps()) {
      //1.1 校验RowBounds
      ensureNoRowBounds();
      //1.2 校验自定义resultHandler
      checkResultHandler();
      //1.3 处理嵌套映射的结果集
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      //2.简单映射的情况
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }
  • 核心流程
1.handleRowValuesForNestedResultMap //处理嵌套映射
2.handleRowValuesForSimpleResultMap //处理简单映射
PS:这一步做一些简单的判断,分两种情况处理

2.4 DefaultResultSetHandler#handleRowValuesForSimpleResultMap

/**
   *  简单ResultMap映射的情况下处理结果行
   * */
  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    //1.创建DefaultResultContext
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    //2.获得ResultSet对象,并跳到 rowBounds 指定的开始位置
    skipRows(rsw.getResultSet(), rowBounds);
    //3.循环处理结果集(shouldProcessMoreRows校验context是否已经关闭和是否达到limit,rsw获取下一条记录)
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      //4.根据该行记录和ResultMap.discriminator ,决定映射使用的 ResultMap 对象
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      //5.根据确定的ResultMap将ResultSet中的该行记录映射为Java对象(处理一行)
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      //6.将映射得到的Java对象添加到ResultHandler.resultList中
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }
  • 核心流程
1.skipRows //定位到正确位置
2.shouldProcessMoreRows //校验,并判断是否超过limit限制
3.resolveDiscriminatedResultMap //处理鉴别器信息
3.getRowValue //将一行记录转换为一个Java对象
4.storeObject //将转化得到的Java对象保存

PS:这一步做了更细节的处理,到了将数据库记录转换为Java对象的部分了。注意这里会循环处理,并且会处理分页的信息。getRowValue是获取记录的核心方法

2.5 DefaultResultSetHandler#getRowValue()

/**
     * 根据确定的ResultMap将ResultSet中的记录映射为Java对象(处理一行)
     * */
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    //创建ResultLoaderMap,和懒加载相关
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //1.创建结果对象
    Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);
    //2.如果hasTypeHandlerForResultObject(rsw, resultMap.getType())返回 true ,意味着rowValue是基本类型,无需执行下列逻辑。
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      //3.创建MetaObject对象,用于访问rowValue对象
      final MetaObject metaObject = configuration.newMetaObject(resultObject);
      //4.foundValues代表,是否成功映射任一属性。若成功,则为true ,若失败,则为false
      boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();
      //5.判断是否开启自动映射功能
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        //6.自动映射未明确的列
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      }
      //7.映射ResultMap中明确映射的列,至此,ResultSet的该行记录的数据已经完全映射到结果对象resultObject的对应属性中
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      //8.如果映射属性失败,则置空 resultObject 对象。
      resultObject = foundValues ? resultObject : null;
      return resultObject;
    }
    return resultObject;
  }
  • 核心流程
1.createResultObject //根据映射规则(resultMap的type信息)创建Java对象
2.shouldApplyAutomaticMappings->applyAutomaticMappings //如果使用了自动映射,那么就会处理未明确映射关系的属性 
3.applyPropertyMappings //处理映射关系明确的属性
4.映射失败则会把结果置为null

PS:getRowValue方法是处理一行记录,再前面的handleRowValuesForSimpleResultMap中会循环调用该方法。getRowValue方法内部会处理映射明确和不明确的列,不明确的列只有在开启了自动映射的情况才会处理,如果存在映射失败,就会将结果置为null,避免错误。

2.6 DefaultResultSetHandler#applyAutomaticMappings

  • 我们看一看DefaultResultSetHandler#applyAutomaticMappings中处理未明确映射关系的列是怎么处理的,对于明确关系的列也比较类似。
/**
   * 自动映射未明确的列
   * */
  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
      //1.获得UnMappedColumnAutoMapping数组
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (autoMapping.size() > 0) {
    //2.遍历UnMappedColumnAutoMapping数组
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
          //3.获得指定字段的值
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
          //4.若非空,标记foundValues有值
        if (value != null) {
          foundValues = true;
        }
        //5.通过metaObject设置到parameterObject中,
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }
  • 核心流程
1.首先会在一个集合里面讲未明确映射的列保存起来,先将他获取出来。
2.然后从数据库记录中,根据列的名称获取到列对应的Value
3.通过反射模块提供的metaObject设值到parameterObject中。注意metaObject是Mybatis反射模块以及封装好的相关类,给属性设值提供简单的使用方式
PS:未明确映射关系的列处理方法就是把属性名当做列名,去数据库记录中获取值,获取到之后通过反射设置到结果对象(有映射关系那么就是通过映射列名去获取值了)

三、辅助类

3.1 ResultSetWrapper

  • ResultSetWrapper内部包装了ResultSet,提供了一些相关的操作,这些操作会在DefaultResultSetHandler中被使用到。在前面的DefaultResultSetHandler#applyAutomaticMappings中我们看到是通过
    ResultSetWrapper变量rsw.getResultSet()来获得指定字段的值。其实ResultSetWrapper可以理解为对原始的结果集进行了包装。在DefaultResultSetHandler#handleResultSets中:
ResultSetWrapper rsw = getFirstResultSet(stmt);
  • 通过Statement获取结果集并封装成ResultSetWrapper对象,ResultSetWrapper对象实际上就包含了一次访问数据库得到的全部结果,并且封装了相关属性,便于ResultSetHandler中进行处理。

3.2 DefaultResultSetHandler#getFirstResultSet

/**
   * 从Statement获取第一个ResultSet结果集对象,封装成ResultSetWrapper对象
   * */
  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    //1.获取ResultSet
    ResultSet rs = stmt.getResultSet();
    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)
      //2.true代表有ResultSet ,false代表没有ResultSet或者是一个更新的结果
      if (stmt.getMoreResults()) {
        //2.1有就获取ResultSet
        rs = stmt.getResultSet();
      } else {
        //2.2没有就break
        if (stmt.getUpdateCount() == -1) {
          // no more results. Must be no resultset
          break;
        }
      }
    }
    //3.返回包装后的ResultSetWrapper
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
  }
  • 核心流程
1. 获取ResultSet
2. 将ResultSet和Configuration配置对象封装到ResultSetWrapper对象中去,后续ResultSetHandler操作结果集实际上是操作ResultSetWrapper。

四、小结

  • 本文给出了ResultSetHandler处理数据库结果集的大体流程,并未细致的分析每一行代码,有兴趣可以自行研究,在掌握了主要的流程之后,在使用的时候遇到问题我们也能够有思路去应对。
  • 下面是前面代码的一个简单流程图展示:

在这里插入图片描述

五、参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值