Mybatis源码分析九之StatementHandler 二(ResultSetHandler)

一、结果集配置

本文主要分析查询结果映射到Java对象的整个过程,首先我们需要知道结果集映射的配置方式以及相应的配置属性。
结果集配置方式主要有两种,resultMap和resultType且只能二选一,resultType很简单,只需要配置类的全限定名。resultMap相对灵活,可以进行复杂的映射关系。Mybatis又提供了三种映射等级NONE、PARTIAL 、FULL,默认是PARTIAL。在select标签中还有一个resultOrdered属性(默认是false)。

那么配置方式和映射等级之间相互组合mybatis是怎么做的?以及各自表达的含义又是什么意思?接下来就跟着源码一探究竟。

二、ResultSetHandler

我们知道ResultSetHandler是在BaseStatementHandler的构造方法中实例化,执行完sql之后就会调用ResultSetHandler的handleResultSets方法,进行结果集的映射,如下所示:

  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    final List<Object> multipleResults = new ArrayList<>();
    int resultSetCount = 0;
    //取出第一个结果集,有的存储过程会返回多个结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);
   //取出对应的resultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    //验证是否存在结果返回类型
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
    //取出结果映射
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //处理结果集
      handleResultSet(rsw, resultMap, multipleResults, null);
     //处理下一个结果集(如果存在多个结果集的时候)
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
    //如果存在多个结果集,并且配置了resultSets属性
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
    //如果当前多个结果集没有读取完,继续读取
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
        //取出嵌套结果集id,并且处理对应结果集
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
    //返回具体的结果
    return collapseSingleResultList(multipleResults);
  }

上述方法主要逻辑就是处理多个结果集情况。具体处理结果集的又交给了方法handleResultSet(rsw, resultMap, multipleResults, null):

  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 {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          //处理结果集
          handleRowValues(rsw,resultMap,defaultResultHandler,rowBounds,null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      closeResultSet(rsw.getResultSet());
    }
  }

handleResultSet方法最后都是调用handleRowValues方法,不同的是会判断是否有父结果集,也就是当前结果集是否配置了extends属性,以及是否设置了自定义的resultHandler 。

  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  //如果存在嵌套结果,也就是当前的resultMap中存在association或collection,并且这两种嵌套不是通过子查询实现的,也就是不是通过设置select属性来得到结果集
    if (resultMap.hasNestedResultMaps()) {
     //确保没有分页
      ensureNoRowBounds();
      //确保没有设置本地的handler
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
    //其它方式都走这里
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

handleRowValues方法区分了简单和复杂结果集的处理,接下来就是对比分析一下。

    private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    //实例化一个结果集上下文
    final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    //取出具体结果集
    ResultSet resultSet = rsw.getResultSet();
    skipRows(resultSet, rowBounds);
    //引用之前的结果,这里用于resultOrdered=true
    Object rowValue = previousRowValue;
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      //是否有鉴别器
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
      //先从嵌套结果对象集合中取出值
      Object partialObject = nestedResultObjects.get(rowKey);
      //resultOrdered=true下
      if (mappedStatement.isResultOrdered()) {
        if (partialObject == null && rowValue != null) {
          nestedResultObjects.clear();
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
      } else {
      //获取当前行的内容
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
        if (partialObject == null) {
        //保存值
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
      }
    }
    //如果当前值不为空并且resultOrdered=true,且还有需要处理的数据
    if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
      previousRowValue = null;
    } else if (rowValue != null) {
      previousRowValue = rowValue;
    }
  }
    private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    //默认结果集上下文
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    //这里就是自带的分页
    skipRows(resultSet, rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
    //先判断是否有带鉴别器
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      //取值
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      //存值
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }

两个方法的不同点在于resultOrdered的实现以及,调用getRowValue方法不同(该方法是个重载的方法),且都有一个共同的调用storeObject方法。
我们先看看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;
      }
      //使用属性映射
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    //返回值
    return rowValue;
  }
    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
    //取出结果集Id
    final String resultMapId = resultMap.getId();
    //partialObject是为了用来处理嵌套结果的缓存对象
    Object rowValue = partialObject;
    if (rowValue != null) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      //把当前的rowValue值保存在ancestorObjects的map集合中
      putAncestor(rowValue, resultMapId);
      //赋值嵌套的结果集
      applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
      //从ancestorObjects移除结果
      ancestorObjects.remove(resultMapId);
    } else {
    //这部分逻辑和简单模式下是一样的逻辑,不同点是isNested这里是true,简单是false
      final ResultLoaderMap lazyLoader = new ResultLoaderMap();
      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, true)) {
          foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        
        putAncestor(rowValue, resultMapId);
        //处理嵌套结果集
        foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
        ancestorObjects.remove(resultMapId);
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
      }
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
        nestedResultObjects.put(combinedKey, rowValue);
      }
    }
    return rowValue;
  }

两个重载的getRowValue方法最大不同点就在于处理嵌套结果集是多了个applyNestedResultMappings方法。所以我们先分析最基本的那个方法。

getRowValue方法中最重要的两个方法就是applyAutomaticMappings和applyPropertyMappings,分别对应着自动映射和属性映射。

applyAutomaticMappings:
先会调用shouldApplyAutomaticMappings方法,判断是否需要进行自动映射,判断逻辑如下:

  private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
  //如果配置了autoMapping属性
    if (resultMap.getAutoMapping() != null) {
      return resultMap.getAutoMapping();
    } else {
    //当前resultMap存在association或collection且不是通过子查询(即没有设置select属性)
      if (isNested) {
        return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
      } else {
      //如果全局配置的自动映射模式不为NONE就会走自动映射
        return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
      }
    }
  }

所以先会判断是否设置了autoMapping属性,然后再判断是否是嵌套查询,再根据具体设置的autoMappingBehavior做判断,所以说如果配置的是NONE,则不会走自动映射方法,这里需要注意的是如果我们结果集中包含了嵌套结果集,且嵌套结果集不是通过子查询(也就是不是通过select属性来得到的结果集,而是通过sql语句的连接查询来实现的),只有在设置自动映射级别为FULL时才会走自动映射。

  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  //取出未设置自动映射的属性,取值逻辑就是解析手动配置的映射关系和查询出的结果集ResultSet得到的结果字段进行筛选。去除掉手动配置的,剩下的就是未设置自动映射的属性
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
      //从数据库返回的结果集取值
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // 赋值
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }

所以自动映射的逻辑比较简单,它是以数据库返回的字段作为参考,然后在去除掉手动设置的属性,把剩下的属性进行映射赋值,接下来我们再看看applyPropertyMappings属性映射方法。

applyPropertyMappings:

  private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    //取出设置了映射关系的列名,且在数据库返回值中存在的列名
    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())) {
          // 赋值
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

通过属性赋值时,取值需要通过getPropertyMappingValue方法获取。

  private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
      //如果当前配置的属性是嵌套查询,当前是处理resultMap中的association或collection配置了select属性
    if (propertyMapping.getNestedQueryId() != null) {
      return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
      //如果配置了resultSet属性
    } 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);
    }
  }

这里就有一个延迟加载的实现过程,如果在resultMap中配置了association或collection且还设置了select属性。那么这个嵌套查询就有一个延迟加载的功能具体的实现是在getNestedQueryMappingValue方法中。

//只取了getNestedQueryMappingValue方法的部分代码
 final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
 //如果配置了延迟加载也就是fetchType=lazy
        if (propertyMapping.isLazy()) {
        //往延迟加载器中添加具体需要加载的值,在createResultObject方法中会判断当前是否有延迟加载属性,如果存在就做一层代理,lazyLoader是参数之一,所以哪些方法设置了可以触发延迟代理的就会调用lazyLoder的load方法
          lazyLoader.addLoader(property, metaResultObject, resultLoader);
          value = DEFERRED;
        } else {
        //否则立即加载
          value = resultLoader.loadResult();
        }

以上大致分析了整个的结果集处理过程,接下来我们分析一下两种结果集配置方式和映射等级的组合下有什么区别。

NONE:
官方解释是禁用自动映射,也就是只会调用applyPropertyMappings方法,如果没有手动设置映射关系,那么不管是设置resultType还是resultMap返回值都是null。如果全局autoMappingBehavior配置的是NONE,那么我们只能用resultMap来配置结果集,然后可以通过resultMap的属性autoMapping=true来打开自动映射。

PARTIAL :
这是autoMappingBehavior的默认值,官方解释是:对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射 ,如果当前配置的是resultType,那么可以完全按照数据库返回的字段属性去进行映射。如果配置的resultMap那么处理逻辑就会根据resultMap中的具体配置来做决定,也是分两种情况:

  1. 如果当前resultMap下配置了association或collection,并且嵌套的结果实现方式是通过sql语句本身的连接查询(join)来实现的,也就是association或collection的属性中没有配置select属性,那么此时就会先判断autoMappingBehavior的值是不是设置的FULL类型,如果不是那么就不能走自动映射
  2. 如果当前resultMap下配置了association或collection,并且嵌套的结果实现方式是配置association或collection的属性中的select属性来实现的,那么此时只要不是配置的是NONE都会走自动映射。

FULL
官方解释是:自动映射所有属性,如果是基于上述2情况下,他的作用和PARTIAL是一样的,如果是1的情况下就需要配置为FULL类型。

有时候我们设计数据库的时候,单词之间使用下划线分割,但是实体对象中又是采用驼峰式,所以此时就需要在全局配置中设置mapUnderscoreToCamelCase=true,会在createAutomaticMappings方法中进行处理。

resultOrdered

这个设置仅针对嵌套结果 select 语句:如果为true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。

摘自官网的解释,但是似乎不是很能明白官网的这段话的具体含义,但是能明白一点的就是,他是针对有嵌套结果集或者是分组的时候使用,从上述分析的源码来看,是在handleRowValuesForNestedResultMap方法中才使用到。也就是说当前的resultMap中包含了association或collection且不是通过select语句来实现的,举例:
一个作者可以发表很多篇文章,假设表中只有两个字段userId、paperId,数据关系如表一所示

userIdpaperId
user1paper1
user1paper2
user1paper3
user2paper4
user2paper5
user2paper6

我们的Java对象实体是:
public class UserPaper{
private String userId;
private List< String> paperIds;
}
我们希望得到的结果是:[UserPaper{userId=user1 , paperIds=[paper1 , paper2,paper3]}, UserPaper{userId=user2, paperIds=[paper3 , paper4,paper5}]
mapper映射文件可以如下配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.UserPaperMapper">
    <cache/>
    <select id="selectAll" resultMap="userPaper"  >
        select * from userPaper  
    </select>
    <resultMap id="userPaper" type="org.apache.ibatis.UserPaper">
        <result property="userId" column="userId"></result>
        <collection property="paperIds" ofType="string">
            <result column="paperId"/>
        </collection>
    </resultMap>
</mapper>

只要运行代码就会得到想要的结果,要弄清楚resultOrdered的工作原理,就得知道mybatis是怎么进行分组操作的,回到如下方法:

  private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    skipRows(resultSet, rowBounds);
    Object rowValue = previousRowValue;
    //此时执行上边的sql语句会得到表一的结果集,接下来我们就按照这个结果集,一行行取值赋值。
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      //分析createRowKey方法可知,键值是通过匹配到的以及未参与分组的属性名和属性值组合而来,也就是上表中的userId这个属性以及他在数据库中的值,那么表一中就可以产生两个key(也就是分成了两组)
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
      //此时需要重点了解nestedResultObjects集合,他是理解resultOrdered关键,第一次取值为空
      Object partialObject = nestedResultObjects.get(rowKey);
      //如果配置了resultOrdered=true
      if (mappedStatement.isResultOrdered()) {
        if (partialObject == null && rowValue != null) {
          nestedResultObjects.clear();
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
        //获取行数据,会传入partialObject
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
      } else {
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
        if (partialObject == null) {
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
      }
    }
  }

这里方法就不在继续展开,我们先简单介绍一下整个分组过程,还是以表一为例。
先取值第一行:此时rowValue得到的结果是[UserPaper{userId=user1 , paperIds=[paper1]}],然后nestedResultObjects中会根据rowKey 保存当前的rowValue值,此时还会保存分组字段的值,也就是paper1的值也会保存到nestedResultObjects集合中(会用同样的规则生成key保存)。
第二行:此时根据key的生成规则我们拿到的rowKey和第一行相同,所以此时会从nestedResultObjects取出第一行的值,赋给
partialObject,此时getRowValue方法就会直接调用applyNestedResultMappings方法,去解析嵌套的结果集。第二行结束的时候会更新当前的rowKey对应的值,也就是当前nestedResultObjects保存的是[UserPaper{userId=user1 , paperIds=[paper1,paper2]}]以及paper1、paper2的值。
第三行:此时nestedResultObjects中的值是[UserPaper{userId=user1 , paperIds=[paper1,paper2,paper3]}]以及paper1、paper2、paper3的值。
从结果来看我们已经完成第一组的分组工作。
第四行:根据rowKey的生成规则,userId的值变了,此时的rowKey也会变,nestedResultObjects得到的结果为null,此时的partialObject =null且rowValue不等于null,所以就会执行

        if (partialObject == null && rowValue != null) {
        //此时就会清空nestedResultObjects缓存
          nestedResultObjects.clear();
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }

所以完成一次分组就会清空一下nestedResultObjects缓存,到这里就可以知道为什么在有嵌套结果集的时候设置resultOrdered=true会减少内存开销。如果我们需要执行大数据分组,那么设置这个会带来很大的性能提升。从上我们也可知nestedResultObjects会保存一个主分组值,以及具体分组字段的值。
注意: resultOrdered从名称来看,结果集是已经排好序的,所以在使用这个属性的时候需要对你的结果集进行排序,如果你查询出来的结果是乱序的,可能会不能正确的分组,所以请慎用

三、总结

本文简单分析了结果集的映射过程,自动映射和属性映射的过程,以及三种不同映射等级的区别,还着重分析了resultOrdered的使用。下文分析mybatis是怎么实现只需要执行一个接口就可以实现数据库的调用。

以上,有任何不对的地方,请指正,敬请谅解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟+1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值