MyBatis印象阅读之ResultSetHandler解析

在在上一章内容中我们还了关于KeyGenerator的技术债,下面还有这些技术债:

parameterHandler
resultSetHandler

今天我们就来偿还关于resultSetHandler的内容。

1. ResultSetHandler解析

首先这是一个接口,我们先来看下这个源码:

public interface ResultSetHandler {

  /**
   * 处理结果映射
   */
   List handleResultSets(Statement stmt) throws SQLException;

  /**
   * 处理游标结果映射,我不太常用,不做展开
   */
   Cursor handleCursorResultSets(Statement stmt) throws SQLException;

  /**
   * 处理存储过程结果映射,我不太常用,不做展开
   */
  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

这里其实我们最常用的也就一个方法:handleResultSets。

我们再来看下它的映射关系
ResultSetHandler继承关系

别以为看到就一个继承方法就可以松口气,打开一看这个类吓死你~~~

不过还是要硬着头皮去看,我们还是一步一步来,首先从它的构造方法开始:

  public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql,
                                 RowBounds rowBounds) {
    this.executor = executor;
    this.configuration = mappedStatement.getConfiguration();
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;
    this.parameterHandler = parameterHandler;
    this.boundSql = boundSql;
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();
    this.reflectorFactory = configuration.getReflectorFactory();
    this.resultHandler = resultHandler;
  }

这里都还行,我们再来看他的主要方法:handleResultSets

  @Override
  public List handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    //获取数据库结果,并装饰了下
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    //获取自己配置的ResultMap
    List resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    //检查rsw与resultMapCount是否合理
    validateResultMapsCount(rsw, resultMapCount);
    //这里逻辑是遍历resultMaps或rsw,一般来说不是存储过程我们的返回结果只有一个Object
    //这里的理解是一个Object可能是List,包含多条记录
    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++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

这个方法乍一看挺少,但是里面的调用比较深,所以看起来会很费劲,所以我们已理解整个思路为主,而不过渡关注细节。

首先我们第一个方法: ResultSetWrapper rsw = getFirstResultSet(stmt);
这我们先不做过多深入,记在技术债里,只要知道这个是封装数据库结果的。

之后在最重要的就是handleResultSet方法,我们来进入:

  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        //对应resultMap进入此处
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          //这边个人感觉除了存储过程是不会进来的,因为之前说了rsw一个只有一个值,而resultHandler大多数不指定,初始都会null
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

这里会跳到我们下一个关键方法handleRowValues来处理行数据:


  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    //处理嵌套映射的情况
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      //处理简单映射情况
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

这里我们只看简单映射的情况:

  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    //根据rowBounds选定相应的值,这里看出我们值分页都是在应用层而非数据库层
    skipRows(resultSet, rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      // 根据该行记录以及 ResultMap.discriminator ,决定映射使用的 ResultMap 对象
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      // 根据最终确定的 ResultMap 对 ResultSet 中的该行记录进行映射,得到映射后的结果对象
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      // 将映射创建的结果对象添加到 ResultHandler.resultList 中保存
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }

  • resolveDiscriminatedResultMap方法我们在一般使用中不会进入,所以不进行深入分析
  • getRowValue 是实际映射行数据,所以我们进行重点查看
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 创建映射后的结果对象,一般是初始化,还没赋值
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      //判断是否开启自动映射功能,默认不开启嵌套的自动映射
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        //自动映射未明确的列
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      //映射 ResultMap 中明确映射的列
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

这里的步骤就和我们自己创建一个结果映射对象的顺序差不多了:

  • 首先实例化一个结果类
  • 开始根据结果插入进行对应的数据库值
    那么下面我们就来看他是如何实现这个的自动化的。

先来看下如果开启了自动化映射功能之后applyAutomaticMappings的方法:


  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    // 获得 UnMappedColumnAutoMapping 数组
    List autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
        //从resultSet中获取对应column对应的值
        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.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }

这里关于createAutomaticMappings方法的逻辑我们也不深入了,我们来看下UnMappedColumnAutoMapping理解他干了哪些就行:

private static class UnMappedColumnAutoMapping {

    /**
     * 字段名
     */
    private final String column;
    /**
     * 属性名
     */
    private final String property;
    /**
     * TypeHandler 处理器
     */
    private final TypeHandler typeHandler;
    /**
     * 是否为基本属性
     */
    private final boolean primitive;

    public UnMappedColumnAutoMapping(String column, String property, TypeHandler typeHandler, boolean primitive) {
        this.column = column;
        this.property = property;
        this.typeHandler = typeHandler;
        this.primitive = primitive;
    }

}

这里理解一下逻辑,遍历未 mapped 的字段的名字的数组,映射每一个字段在结果对象的相同名字的属性,最终生成 UnMappedColumnAutoMapping 对象。

看到自动化映射之后,我们再找来看下默认的映射方式applyPropertyMappings:

 private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    // 获得 mapped 的字段的名字的数组
    final List mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      if (propertyMapping.getNestedResultMapId() != null) {
        // the user added a column attribute to a nested result map, ignore it
        column = null;
      }
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {
        //获取对应的值
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // issue #541 make property optional
        final String property = propertyMapping.getProperty();
        if (property == null) {
          continue;
        } else if (value == DEFERRED) {
          foundValues = true;
          continue;
        }
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

这个方法的逻辑比较简单,通过我们设置的resultMap配置来获取数据库对应的值并映射进去。这里不管是什么方式,我们可以看到映射方法都是通过metaObject.setValue(property, value);

上面还有一个比较关键的方法getPropertyMappingValue:

  private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    //嵌套查询
    if (propertyMapping.getNestedQueryId() != null) {
      return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if (propertyMapping.getResultSet() != null) {
      //存储过程相关,忽略
      addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
      return DEFERRED;
    } else {
      //直接获取
      final TypeHandler typeHandler = propertyMapping.getTypeHandler();
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      return typeHandler.getResult(rs, column);
    }
  }

2.今日总结

今天我们主要分析的是MyBatis是如何自动映射结果对象的,设计到的过程也相对比较负责,中心思想就是通过resultMap的配置,去数据库取,并通过MeteObject辅助类反射进去值。
这里我们又欠下了关于ResultSetWrapper的技术债,我们现在来整理下:

parameterHandler
ResultSetWrapper

你可能感兴趣的:(MyBatis印象阅读之ResultSetHandler解析)