美文网首页
MyBatis印象阅读之ResultSetHandler解析

MyBatis印象阅读之ResultSetHandler解析

作者: 向光奔跑_ | 来源:发表于2019-08-14 11:40 被阅读0次

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

    parameterHandler
    resultSetHandler

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

    1. ResultSetHandler解析

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

    public interface ResultSetHandler {
    
      /**
       * 处理结果映射
       */
      <E> List<E> handleResultSets(Statement stmt) throws SQLException;
    
      /**
       * 处理游标结果映射,我不太常用,不做展开
       */
      <E> Cursor<E> 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<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    
        final List<Object> multipleResults = new ArrayList<>();
    
        int resultSetCount = 0;
        //获取数据库结果,并装饰了下
        ResultSetWrapper rsw = getFirstResultSet(stmt);
    
        //获取自己配置的ResultMap
        List<ResultMap> 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<Object> 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<Object> 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<UnMappedColumnAutoMapping> 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<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
        boolean foundValues = false;
        final List<ResultMapping> 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解析

          本文链接:https://www.haomeiwen.com/subject/umpwjctx.html