Mybatis 源码 ④ :TypeHandler

文章目录

  • 一、前言
  • 二、DefaultParameterHandler
    • 1. DefaultParameterHandler#setParameters
      • 1.1 UnknownTypeHandler
      • 1.2 自定义 TypeHandler
  • 三、DefaultResultSetHandler
    • 1. hasNestedResultMaps
    • 2. handleRowValuesForNestedResultMap
      • 2.1 resolveDiscriminatedResultMap
      • 2.2 createRowKey
      • 2.3 getRowValue
        • 2.2.1 createResultObject
        • 2.2.2 applyAutomaticMappings
        • 2.2.3 applyPropertyMappings
        • 2.2.4 applyNestedResultMappings
      • 2.4 storeObject
    • 3. handleRowValuesForSimpleResultMap

一、前言

Mybatis 官网 以及 本系列文章地址:

  1. Mybatis 源码 ① :开篇
  2. Mybatis 源码 ② :流程分析
  3. Mybatis 源码 ③ :SqlSession
  4. Mybatis 源码 ④ :TypeHandler
  5. Mybatis 源码 ∞ :杂七杂八

书接上文 Mybatis 源码 ③ :SqlSession
。我们这里来看下 DefaultParameterHandler 和 DefaultResultSetHandler 的处理过程。

二、DefaultParameterHandler

DefaultParameterHandler 类图如下,可以看到其实现了 ParameterHandler 接口,我们可以通过 Plugin 的方式对 ParameterHandler 进行增强。这里我们主要来看 DefaultParameterHandler 的具体作用。
在这里插入图片描述

1. DefaultParameterHandler#setParameters

在 SimpleExecutor 和 BaseExecutor doUpdate、doQuery、doQueryCursor 等方法中会调用 prepareStatement 方法,在其中会调用 StatementHandler#parameterize 来对参数做预处理,里面会调用 PreparedStatementHandler#parameterize,该方法如下:

  @Override
  public void parameterize(Statement statement) throws SQLException {
    // 这里会调用 DefaultParameterHandler#setParameters
    parameterHandler.setParameters((PreparedStatement) statement);
  }

因此我们可以知道,在Sql 执行前,会调用 DefaultParameterHandler#setParameters 方法来对参数做处理,这也就给了 TypeHandler 的参数转换提供了条件。


DefaultParameterHandler#setParameters 实现如下:

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 获取当前Sql执行时的参数
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          // 对一些额外参数处理
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          	// 判断是否有合适的类型转换器,可以解析当前参数
          	// 这里个人理解是为了判断参数是否是单独参数,
            value = parameterObject;
          } else {
          	// 根据 参数名去获取参数传入的值。
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // 如果当前参数指定了类型转换器, 则通过类型转换器进行转换。否则交由 UnknownTypeHandler 
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
          	// 调用类型转换器进行处理, 默认情况下是 UnknownTypeHandler 
          	// jdbcType 是我们通过 jdbcType 属性指定的类型,没有指定则为空 
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

上面可以看到逻辑比较简单:遍历所有参数,并且参数值交由 typeHandler.setParameter 来处理。需要注意的是这里的 typeHandler 如果没有指定默认是 UnknownTypeHandler。在UnknownTypeHandler 中则会根据参数实际类型来从注册的 TypeHandler 中选择合适的处理器来处理。下面我们具体来看。

1.1 UnknownTypeHandler

UnknownTypeHandler#setParameter 会调用 UnknownTypeHandler#setNonNullParameter, 我们以该方法为例,UnknownTypeHandler 的其他方法也类似。

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
      throws SQLException {
    // 根据参数类型来获取 类型处理器
    // jdbcType 是我们通过 jdbcType 属性指定的类型,没有指定则为空 
    TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
    // 调用类型处理器处理
    handler.setParameter(ps, i, parameter, jdbcType);
  }

  private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
    TypeHandler<?> handler;
    // 参数为空直接返回 ObjectTypeHandler
    if (parameter == null) {
      handler = OBJECT_TYPE_HANDLER;
    } else {
      // 从注册的 TypeHandler 中根据类型选择合适的处理器
      handler = typeHandlerRegistrySupplier.get().getTypeHandler(parameter.getClass(), jdbcType);
      // check if handler is null (issue #270)
      // 如果没找到返回 ObjectTypeHandler
      if (handler == null || handler instanceof UnknownTypeHandler) {
        handler = OBJECT_TYPE_HANDLER;
      }
    }
    return handler;
  }

这里可以看到, 在执行Sql前会通过 DefaultParameterHandler#setParameters 对参数做一次处理。

  1. 如果参数指定了 typeHandler 则使用参数指定的 TypeHandler
  2. 如果参数没有指定,则使用 UnknownTypeHandler 来处理。而 UnknownTypeHandler 会根据参数的实际类型和 jdbcType 来从已注册的 TypeHandler 选择合适的处理器对参数做处理。

1.2 自定义 TypeHandler

我们可以自定义 TypeHandler 来实现指定字段的特殊处理,如用户密码在数据库中不能明文展示,而在代码中我们明文处理,则就可以通过如下方式定义:

  1. 创建一个 PwdTypeHandler 类,继承 BaseTypeHandler
public class PwdTypeHandler extends BaseTypeHandler<String> {
	// 定义加解密方式
    private static final SymmetricCrypto AES = new SymmetricCrypto(
            SymmetricAlgorithm.AES, "1234567890123456".getBytes());

	// 赋值时加密
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, AES.encryptBase64(parameter));
    }
	// 取值时解密
    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return AES.decryptStr(rs.getString(columnName));
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return AES.decryptStr(rs.getString(columnIndex));
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return AES.decryptStr(cs.getString(columnIndex));
    }
}
  1. XML 指定使用的 typeHandler,如下
    <resultMap id="BaseResultMap" type="com.kingfish.entity.SysUser">
        <!--@Table sys_user-->
        <result property="id" column="id" jdbcType="INTEGER"/>
        <result property="userName" column="user_name" jdbcType="VARCHAR"/>
        <result property="password" column="password" jdbcType="VARCHAR" typeHandler="com.kingfish.config.handler.PwdTypeHandler"/>
        <!-- 忽略其他字段 -->
    </resultMap>
  1. 在实际调用接口时新增或返回时都会使用 PwdTypeHandler 来对指定字段做处理人,如下:
    1. 调用接口明文新增时入库是加密后结果 在这里插入图片描述
    2. 数据库加密,查询返回是明文
      在这里插入图片描述

三、DefaultResultSetHandler

DefaultResultSetHandler实现了ResultSetHandler 接口,ResultSetHandler 见名知意,即为结果集合处理器。所以下面我们来看看该方法的具体逻辑 :

  @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;
    // 获取第一个结果集  ResultSet 并包装成 ResultSetWrapper 
    ResultSetWrapper rsw = getFirstResultSet(stmt);
	
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    // ResultMap 的数量, 当使用存储过程时,可能会有多个,我们这里不考虑存储过程的多个场景。
    int resultMapCount = resultMaps.size();
    // ResultMap 数量校验 :rsw != null && resultMapCount < 1
    validateResultMapsCount(rsw, resultMapCount);/**********************************************************************/
    // 1.对 ResultMap 的处理
    // 循环所有的 ResultMap
    while (rsw != null && resultMapCount > resultSetCount) {
     // 获取当前 ResultMap
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 1.1 根据ResultMap中定义的映射规则处理ResultSet,并将映射得到的Java对象添加到 multipleResults集合中保存
      handleResultSet(rsw, resultMap, multipleResults, null);
      // 1.2 获取下一个 ResultSet 
      rsw = getNextResultSet(stmt);
      // 1.3 清理nestedResultObjects集合,这个集合是用来存储中间数据的
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
	/**********************************************************************/
    // 2. 对 ResultSets 的处理
	// 对 resultSet 处理,<select>标签可以通过 resultSets 属性指定
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      // 处理reusltSet 
      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);
        }
        // 获取下一个 ResultSet
        rsw = getNextResultSet(stmt);
        // 清理nestedResultObjects集合,这个集合是用来存储中间数据的
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
    /**********************************************************************/
	// 返回结果集
    return collapseSingleResultList(multipleResults);
  }

这里可以看到, DefaultResultSetHandler#handleResultSet 方法的逻辑分为对 ResultMap 的处理和 对 ResultSets 的处理,在涉及存储过程的情况下会返回 ResultSets ,该部分不在本文的讨论范围内,在 Mybatis 官方文档 中对该属性做了具体的描述 : 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。具体使用如下图:
在这里插入图片描述
业务使用方面可以详参: https://blog.csdn.net/qq_40233503/article/details/94436578


本文主要看对 ResultMap 的处理内容,而其中最主要的则是 DefaultResultSetHandler#handleResultSet 方法,具体实现如下:

 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      // 父级 mapper 不为空的情况 :在处理 ResultSet 时会出现,不在本文讨论范围
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        // 1. 未指定 ResultHandler 情况 : 如果 resultHandler  为空则创建一个 DefaultResultHandler 作为默认处理器
      	// 这里的 resultHandler 是我们调用 Mapper Interface Method 方法时指定的。如果没指定则为空
        if (resultHandler == null) {
          // 如果没指定则使用默认的 DefaultResultHandler 来处理结果
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          // 2. 指定了 ResultHandler 情况 : 将 resultHandler 传入作为结果处理器
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

上面可以看到这里针对了 未指定 ResultHandler 情况 和 指定了 ResultHandler 情况做了判断:我们可以在 Mapper Interface Method 入参中传入 ResultHandler 来对返回结果集做处理。(也可传入 RowBounds 对返回结果集做逻辑分页,但是需要注意 RowBounds 仅是逻辑分页,数据已经查出,所以不建议使用),通过实现ResultHandler 接口来对该查询的结果进行定制化解析(需要注意方法不能有返回值,因为返回值已经交由 resultHandler 来处理了),当 Mybatis 将结果查询出后会交由 resultHandler#handleResult 方法来处理。在方法入参中传入 ResultHandler 实例,并且返回值为 void,如下指定了 selectByParam 方法查询的结果交由 ResultHandler 来处理:

    void selectByParam(ResultHandler resultHandler);

而实际上无论 ResultHandler 指定与否,都会调用 DefaultResultSetHandler#handleRowValues 方法来解析行数据,所以我们来看看该方法的具体实现,如下:

  // 处理行数据 : 该方法会获取并解析出来每一行的数据
  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  	//  1. 如果有嵌套的 ResultMap,即 ResultMap#hasNestedResultMaps = true
    if (resultMap.hasNestedResultMaps()) {
      // 嵌套前的判断1 :嵌套情况下,如果 safeRowBoundsEnabled 为true,则不能使用 RowBounds (确切的说只能使用 默认的 RowBounds )
      // safeRowBoundsEnabled 可以通过 {mybatis.configuration.safe-row-bounds-enabled} 配置,代表 允许在嵌套语句中使用分页(RowBounds)	, 默认 true
      ensureNoRowBounds();
      // 嵌套前的判断2 :嵌套情况下,如果 safeResultHandlerEnabled 为 true && 语句属性 resultOrdered 为 true 则抛出异常
      // safeResultHandlerEnabled 可以通过 {mybatis.configuration.safe-result-handler-enabled} 配置,代表 允许在嵌套语句中使用分页(ResultHandler)。默认 true
      checkResultHandler();
      // 2. 处理嵌套 ResultMap
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      // 3. 无嵌套 ResultMap的 简单逻辑
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

这里可以看到,对于行数据的处理分为嵌套情况和非嵌套情况,如下 :

  1. DefaultResultSetHandler#hasNestedResultMaps :通过 ResultMap#hasNestedResultMaps 属性判断当前是否是嵌套结果集,成立条件是 <resultMap> 标签中使用了子标签 <association><collection> ,并且标签没有指定 select 属性 或使用了 <case> 标签。(如果指定了select属性,则会保存在 ResultMapping#nestedQueryId 指定 查询id)。 该属性在于 ResultMap.Builder#build 中会初始化,如下:
    在这里插入图片描述

  2. DefaultResultSetHandler#handleRowValuesForNestedResultMap :用来处理嵌套结果集的情况,即如果上面的判断成立了,则执行该逻辑。

  3. DefaultResultSetHandler#handleRowValuesForSimpleResultMap :用来处理简单查询的情况,无嵌结果集的情况。


下面我们详细来看上面的详细逻辑

1. hasNestedResultMaps

DefaultResultSetHandler#hasNestedResultMaps 方法的作用是判断当前 ResultMap 是否是嵌套结果集,其判断依据是 ResultMap#hasNestedResultMaps = true,如下:

  public boolean hasNestedResultMaps() {
    return hasNestedResultMaps;
  }

而 ResultMap#hasNestedResultMaps 属性的初始化是在ResultMap.Builder#build 中完成,如下:

在这里插入图片描述

这里我们关注两个属性:ResultMap#hasNestedQueries (标记当前 ResultMap 是否有嵌套映射,判断依据是 ResultMapping#nestedQueryId != null)和 ResultMap#hasNestedResultMaps (标记当前 ResultMap 是否有嵌套结果集,判断依据是 ResultMapping#nestedResultMapId != null || ResultMapping#resultSet != null


我们以 XML 解析为例,在 XMLMapperBuilder#buildResultMappingFromContext中,会通过如下逻辑来解析取 nestedSelect、nestedResultMap 属性 :

在这里插入图片描述
并且在 MapperBuilderAssistant#buildResultMapping 方法中根据 nestedSelect、nestedResultMap 来给 ResultMapping#nestedQueryId 和 ResultMapping#nestedResultMapId 赋值,如下:
在这里插入图片描述


综上,这里的嵌套判断成立的条件是 :<resultMap> 标签中使用了子标签 <association><collection> ,并且标签没有指定 select 属性 或使用了 <case> 标签。(如果指定了select属性,则会保存在 ResultMapping#nestedQueryId 指定 查询id)。下面我们来简单介绍下这两种情况的区别。


对于嵌套映射,其存在两种实现方式:

  • 内部嵌套 : 使用 association、collection 标签但是不指定 select 属性,或使用case 标签。这种是通过一条 Sql 语句查询后关联处理。 下面DefaultResultSetHandler#handleRowValuesForNestedResultMap 的方法就是处理该情况
  • 外部嵌套 : 使用 association、collection 标签并指定 select 属性。这种是通过一条Sql语句执行后再根据select指定语句关联查询。下面DefaultResultSetHandler#applyPropertyMappings 中会对这种嵌套查询做处理

我们以如下两个表为例:

CREATE TABLE `sys_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
  `user_name` varchar(255) DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) DEFAULT NULL COMMENT '密码',
  `role_id` bigint(20) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;


CREATE TABLE `sys_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
  `role_name` varchar(255) DEFAULT NULL COMMENT '用户名',
  `status` varchar(255) DEFAULT NULL COMMENT '状态',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
  1. 内部嵌套实现如下:

        <resultMap id="UserBaseResultMap" type="com.kingfish.entity.SysUser">
            <result property="id" column="id" jdbcType="INTEGER"/>
            <result property="userName" column="user_name" jdbcType="VARCHAR"/>
            <result property="password" column="password" jdbcType="VARCHAR" />
          	<!-- 忽略余下属性 -->
        </resultMap>
        
        <resultMap id="BaseResultMap" type="com.kingfish.entity.SysRole">
            <!--@Table sys_role-->
            <result property="id" column="id" jdbcType="INTEGER"/>
            <result property="roleName" column="role_name" jdbcType="VARCHAR"/>
            <result property="status" column="status" jdbcType="VARCHAR"/>
            <!-- 忽略余下属性 -->
        </resultMap>
    
    	<!-- 内部嵌套映射 -->
        <resultMap id="InnerNestMap" type="com.kingfish.entity.dto.SysRoleDto" extends="BaseResultMap">
        	<!-- 指定 sysUsers 属性都是前缀为 user_ 的属性 -->
            <collection property="sysUsers" columnPrefix="user_"
                        resultMap="UserBaseResultMap"></collection>
        </resultMap>
    	<!-- 通过联表查询出来多个属性,如果属性名跟 sysUsers 对应的com.kingfish.dao.SysUserDao.BaseResultMap配置的属性名一致则会映射上去 (属性名映射规则受到columnPrefix影响) -->
        <select id="selectRoleUser" resultMap="InnerNestMap">
            select sr.*, su.id user_id, su.user_name user_user_name, su.password user_password
            from sys_role sr
                     left join sys_user su on sr.id = su.role_id
        </select>
    
  2. 外部嵌套实现如下:

    	<resultMap id="UserBaseResultMap" type="com.kingfish.entity.SysUser">
            <result property="id" column="id" jdbcType="INTEGER"/>
            <result property="userName" column="user_name" jdbcType="VARCHAR"/>
            <result property="password" column="password" jdbcType="VARCHAR" />
          	<!-- 忽略余下属性 -->
        </resultMap>
        
    	<!-- 外部嵌套映射 -->
        <resultMap id="OutNestMap" type="com.kingfish.entity.dto.SysRoleDto" extends="BaseResultMap">
        	<!-- 指定使用selectUser 作为 sysUsers 属性的查询语句 -->
            <collection property="sysUsers" ofType="com.kingfish.entity.dto.SysUserDto"
                        select="selectUser" column="{roleId=id}" ></collection>
        </resultMap>
    
        <select id="selectUser" resultMap="UserBaseResultMap">
            select
                id,  user_name, password
            from sys_user
            where role_id = #{roleId}
        </select>
    
        <select id="selectRole" resultMap="OutNestMap">
            select *
            from sys_role
        </select>
    
    

上述两种查询的返回结果都相同,如下:
在这里插入图片描述

关于该部分内容本文只做简单介绍,如有需要可详参:https://www.cnblogs.com/sanzao/p/11466496.html#_label1


2. handleRowValuesForNestedResultMap

上面我们介绍了嵌套条件成立的条件,当满足了上述条件后,说明了当前查询存在嵌套结果集,则调用 DefaultResultSetHandler#handleRowValuesForNestedResultMap 来处理,具体如下

  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();
    // 跳过执行行数据,由 RowBounds.offset 属性决定
    skipRows(resultSet, rowBounds);
    Object rowValue = previousRowValue;
    // 确定当前剩余数据满足条件,即此次拉取的数据量 < RowBounds.limmit  时 且 连接未关闭 且后续还有结果集,则再次获取
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
       // 1. 解析 discriminator 属性,获取 discriminator 指定的 ResultMap 
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      // 2. 创建当前行记录的 缓存 key
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
      // 尝试获取该行记录的缓存
      Object partialObject = nestedResultObjects.get(rowKey);
      // issue #577 && #542
      // resultOrdered = true 时
      if (mappedStatement.isResultOrdered()) {
      	// 如果未缓存安全数据
        if (partialObject == null && rowValue != null) {
          // 清空缓存
          nestedResultObjects.clear();
          // 存储数据
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
        // 3. 获取行数据 
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
      } else {
        // 3. 获取行数据
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
        // 4. 存储数据 : partialObject == null 说明数据没有被缓存
        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;
    }
  }

这里我们可以看到 :

  1. 利用 RowBounds 是可以实现分页的功能的,但却是一个逻辑分页,因为所有数据都是已经加载到内存后再根据 RowBounds 的分页限制选择是否丢弃或继续获取,因此并不建议使用。
  2. resolveDiscriminatedResultMap 方法实现了对 <discriminator > 标签的解析,并将 <discriminator >解析后的ResultMap 作为最终的 ResultMap 处理。下面我们会详细讲。
  3. getRowValue 方法会根据 resultMap 解析并获取当前的行数据。下面我们会详细讲。
  4. storeObject 方法会将处理后的行结果缓存起来。下面我们会详细讲。

2.1 resolveDiscriminatedResultMap

该方法的作用是为了解析 <discriminator> 标签, 内容比较简单,这里不在过多赘述。关于 <discriminator> 标签的用法,如有需要详参 Mybatis 源码 ∞ :杂七杂八

  public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
    Set<String> pastDiscriminators = new HashSet<>();
    Discriminator discriminator = resultMap.getDiscriminator();
    while (discriminator != null) {
      // 获取 discriminator 指定的 column 的值
      final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
      // 根据 column 的值来判断执行哪个 case 分支 : 根据 value 获取到  discriminatedMapId ,如果获取到则说明有对应的 case 分支
      final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
      // 如果存在该 ResultMap 
      if (configuration.hasResultMap(discriminatedMapId)) {
      	// 用 discriminator 指定 ResultMap 替换现有的 resultMap 
        resultMap = configuration.getResultMap(discriminatedMapId);
        Discriminator lastDiscriminator = discriminator;
        discriminator = resultMap.getDiscriminator();
        if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
          break;
        }
      } else {
        break;
      }
    }
    return resultMap;
  }

2.2 createRowKey

createRowKey 方法 作用是创建当前行的缓存Key。具体实现如下:

  // 生成行数据的缓存Key,这里会将列名和列值都作为关键值创建Key
  // 在嵌套映射中会作为唯一标志标识一个结果对象
  private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
    final CacheKey cacheKey = new CacheKey();
    // 使用映射结果集的id 作为 CacheKey 的一部分
    cacheKey.update(resultMap.getId());
    // 获取 <result> 标签结果集
    List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
    // 为空则判断返回类型是是不是Map
    if (resultMappings.isEmpty()) {
      if (Map.class.isAssignableFrom(resultMap.getType())) {
      	// 由结果集中的所有列名以及当前记录行的所有列值一起构成CacheKey
        createRowKeyForMap(rsw, cacheKey);
      } else {
      	// 由结果集中未映射的列名以及它们在当前记录行中的对应列值一起构成CacheKey对象
        createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
      }
    } else {
     //  由ResultMapping集合中的列名以及它们在当前记录行中相应的列值一起构成CacheKey
      createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
    }
    // 如果除了映射结果集的id 之外没有任何属性参与生成CacheKey 则返回NULL_CACHE_KEY
    if (cacheKey.getUpdateCount() < 2) {
      return CacheKey.NULL_CACHE_KEY;
    }
    return cacheKey;
  }


这里我们不再具体分析具体的代码内容,直接总结具体的逻辑(下面内容来源 Mybatis源码阅读(三):结果集映射3.2 —— 嵌套映射):

  1. 尝试使用节点或者节点中定义的列名以及该列在当前记录行中对应的列值生成CacheKey
  2. 如果ResultMap中没有定义这两个节点,则有ResultMap中明确要映射的列名以及它们在当前记录行中对应的列值一起构成CacheKey对象
  3. 经过上面两个步骤后如果依然查不到相关的列名和列值,且ResultMap的type属性明确指明了结果对象为Map类型,则有结果集中所有列名以及改行记录行的所有列值一起构成CacheKey
  4. 如果映射的结果对象不是Map,则由结果集中未映射的列名以及它们在当前记录行中的对应列值一起构成CacheKey

额外需要注意的是 ,CacheKey 创建后,会尝试从 nestedResultObjects 中获取对象对数据。如下:

      Object partialObject = nestedResultObjects.get(rowKey);

nestedResultObjects 的作用是缓存所有查询出的结果数据,但是这里会存在问题:在嵌套映射时如果存在两行完全一样的数据,则会被忽略。该问题我们在 Mybatis 源码 ∞ :杂七杂八 进行了详细说明

2.3 getRowValue

getRowValue 方法是处理每一行的值,需要注意的是这里的 handleRowValuesForNestedResultMap 中调用的 getRowValue 方法和 handleRowValuesForSimpleResultMap 中调用的 getRowValue 方法是重载方法。


下面我们来具体看 handleRowValuesForNestedResultMap 中调用的 getRowValue 如下:

 // DefaultResultSetHandler#getRowValue(ResultSetWrapper, ResultMap, .CacheKey, String, Object)
 // 将数据库查出来的数据转换为  Mapper Interface Method 返回的类型
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
  	// 获取 ResultMap 的唯一ID
    final String resultMapId = resultMap.getId();
    // 外层数据赋值给 rowValue
    Object rowValue = partialObject;
    // 如果缓存有值,则认为是嵌套映射
    if (rowValue != null) {
      // 用外层数据生成元数据 metaObject 
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      // 外层数据保存到 ancestorObjects 中
      putAncestor(rowValue, resultMapId);
      // 处理嵌套逻辑
      applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
      // 从 ancestorObjects 中移除该数据
      ancestorObjects.remove(resultMapId);
    } else {
      final ResultLoaderMap lazyLoader = new ResultLoaderMap();
      //  1. 反射 Mapper Interface Method 返回的类型对象,这里尚未填充行数据
      rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
      // rowValue 不为空 && 没有针对 rowValue 类型的 TypeHandler 
      if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        // 如果允许自动映射(可通过 <resultMap> 标签的 autoMapping 属性指定)
        if (shouldApplyAutomaticMappings(resultMap, true)) {
          // 2. 根据自动映射规则尝试映射,看是行数据是否能映射到对应的属性 (忽略大小写的映射)
          foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        // 3. 根据属性映射
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        putAncestor(rowValue, resultMapId);
        // 4. 对嵌套结果集进行映射
        foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
        ancestorObjects.remove(resultMapId);
        foundValues = lazyLoader.size() > 0 || foundValues;
        // 如果 映射到了属性值 或者 配置了空数据返回实体类 (mybatis.configuration.return-instance-for-empty-row 属性指定)则 返回 rowValue, 否则返回空 
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
      }
      // 缓存外层对象
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
        nestedResultObjects.put(combinedKey, rowValue);
      }
    }
    return rowValue;
  }

上面我们我们主要看下面几个方法:

  1. createResultObject : 这里会创建Mapper Interface Method 返回的类型对象,但是并没有对各个属性赋值。不过需要注意 createResultObject 方法创建返回对象时分为下面集中情况:

    1. 如果Mybatis 中注册了针对 ResultMap.type 类型的 TypeHandler,则会调用 TypeHandler#getResult 来获取结果
    2. 如果当前 ResultMap 指定了构造函数参数,则使用指定入参构造结果
    3. 如果 ResultMap.type 是接口类型或者 ResultMap.type 有默认构造函数,则通过 ObjectFactory#create 创建构造函数
    4. 如果开启了自动映射则按构造函数签名创建
    5. 如果上述情况都没匹配,则抛出异常。
  2. applyAutomaticMappings :如果开启了自动映射则会按照自动映射的规则(忽略属性大小写差异)进行属性映射

  3. applyPropertyMappings :根据规则对剩余属性进行映射

  4. applyNestedResultMappings : 处理嵌套映射的属性。


下面我们详细来看上面的几个方法的具体实现:

2.2.1 createResultObject

createResultObject 实现如下:

  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    this.useConstructorMappings = false; // reset previous mapping result
    final List<Class<?>> constructorArgTypes = new ArrayList<>();
    final List<Object> constructorArgs = new ArrayList<>();
    // 根据 ResultMap 的属性通过反射方式创建一个对象(如果通过 <constructor>指定了构造参数 则注入构造参数)
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    // 对象不为空且没有对应的类型处理器
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      // 遍历所有属性
      for (ResultMapping propertyMapping : propertyMappings) {
        // issue gcode #109 && issue #149
        // 如果是嵌套结果集 && 并且开启了懒加载,则这里创建一个代理对象,等实际调用时才会触发获取逻辑
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
          break;
        }
      }
    }
    // 标注是否使用了构造映射
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
    return resultObject;
  }

2.2.2 applyAutomaticMappings

如果开启了自动映射则会按照自动映射的规则(忽略属性大小写差异)进行属性映射

  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    // 获取开启自动映射的结果集
    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)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }

2.2.3 applyPropertyMappings

这里是对剩下的属性进行映射,在上面我们提到过嵌套映射存在内部嵌套和外部嵌套两种情况。这里则会对外部嵌套的情况做处理。具体如下:

  private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    // 获取使用 columnPrefix拼接后的列名
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    // 获取 ResultMap 的 reuslt 属性
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    // 遍历所有属性
    for (ResultMapping propertyMapping : propertyMappings) {
      // 获取拼接 columnPrefix 后 属性列名
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      // 如果当前属性存在嵌套的 ResultMap 则忽略该列,交由下面进行嵌套解析
      if (propertyMapping.getNestedResultMapId() != null) {
        // the user added a column attribute to a nested result map, ignore it
        column = null;
      }
      // 如果当前查询有复合结果(嵌套映射时,可能出现一对一、一对多的情况) || 当前列匹配(property 与 column经过转换后一致)	|| 当前属性指定了 ResultSet
      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;
        }
        // value 不为空  || (配置 {mybatis.configuration.call-setters-on-nulls} 为 true && set 方法不为私有) 
        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;
  }


可以看到上面的关键方法在于下面 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 {
      // 最基础的解析使用指定的 TypeHandler 解析数据并返回。如 Long 使用 LongTypeHandler等
      final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
      // 拼接前缀:即 prefix + columnName
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      // 获取处理结果并返回
      return typeHandler.getResult(rs, column);
    }
  }

上面我们可以看到,这里分成三种情况

  1. 外部嵌套:交由 getNestedQueryMappingValue 方法来处理
  2. 指定 ResultSet : 挂起子关系,等后续一起处理(不在本文分析内容)
  3. 最基础的解析:交由 TypeHandler 来获取结果集并返回对象

下面我们来看看 getNestedQueryMappingValue 嵌套解析的过程:

  // 获取嵌套查询的结果集映射
  private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    // 获取嵌套映射id 即 select属性指定的查询语句
    final String nestedQueryId = propertyMapping.getNestedQueryId();
    final String property = propertyMapping.getProperty();
    // 获取嵌套映射指定的语句
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
    // 获取参数类型
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
    // 获取嵌套映射的参数
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
    Object value = null;
    if (nestedQueryParameterObject != null) {
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
      final Class<?> targetType = propertyMapping.getJavaType();
      // 如果结果已经被缓存
      if (executor.isCached(nestedQuery, key)) {
      	// 延迟加载
        executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
        value = DEFERRED;
      } else {
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
        // 如果是懒加载则加载到 lazyLoader中并返回推迟加载对象
        if (propertyMapping.isLazy()) {
          lazyLoader.addLoader(property, metaResultObject, resultLoader);
          value = DEFERRED;
        } else {
          // 加载结果
          value = resultLoader.loadResult();
        }
      }
    }
    return value;
  }

2.2.4 applyNestedResultMappings

applyNestedResultMappings 则是针对内部嵌套进行处理,如下:

  private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
    boolean foundValues = false;
    for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
      final String nestedResultMapId = resultMapping.getNestedResultMapId();
      if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
        try {
          // 获取拼接 parentPrefix 后的列名 :我们可以通过 <collection> <association> 的 columnPrefix 属性指定前缀,这里会进行前缀拼接
          final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
          // 1. 获取嵌套映射对应的结果集
          final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
          // 如果列前缀为空:一般情况下如果使用嵌套映射则会声明前缀
          if (resultMapping.getColumnPrefix() == null) {
            // try to fill circular reference only when columnPrefix
            // is not specified for the nested result map (issue #215)
            // 尝试获取祖先对象
            Object ancestorObject = ancestorObjects.get(nestedResultMapId);
            if (ancestorObject != null) {
              // 如果是新对象,则进行链接 : 当第一次处理当前嵌套映射时认为是新对象,可以简单认为没有放入 nestedResultObjects 缓存
              if (newObject) {
              	// 链接对象
                linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
              }
              continue;
            }
          }
          // 创建行的key
          final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
          // 与父级 key 进行组合:
          final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
          // 从缓存中获取该行对象
          Object rowValue = nestedResultObjects.get(combinedKey);
          boolean knownValue = rowValue != null;
          // 如果对象是集合类型,则判断是否需要初始化,需要则创建爱你
          instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
          // 据notNullColumn属性, 检测是否有非空属性,如果全为空则没必要解析
          if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
            // 获取映射结果
            rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
            // 如果映射结果不为空 && 不是缓存对象 则链接对象
            // 这里的判断会引发一个问题 : 在嵌套映射时如果两个对象完全一致会被缓存命中从而不会链接对象,导致数据丢失,下面会讲
            if (rowValue != null && !knownValue) {
              linkObjects(metaObject, resultMapping, rowValue);
              foundValues = true;
            }
          }
        } catch (SQLException e) {
          throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
        }
      }
    }
    return foundValues;
  }

  // 链接对象
  private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
  	// 必要的话初始化集合对象
    final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
    // 如果集合对象不为空,则添加到集合对象中
    if (collectionProperty != null) {
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
      targetMetaObject.add(rowValue);
    } else {
    // 否则的话保存属性到元数据中
      metaObject.setValue(resultMapping.getProperty(), rowValue);
    }
  }

这里需要注意的是由于 Mybatis 的 RowKey 是属性名 + 属性值拼接,在嵌套时如果两行数据完全一致,则第一行数据会被缓存,当处理第二行数据时,会被缓存命中从而不满足 rowValue != null && !knownValue 的判断条件,导致数据丢失。

2.4 storeObject

storeObject 方法将数据保存起来, 具体实现如下:

  private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
  	// 如果父 ResultMap 存在 (嵌套模式),则链接到 父 ResultMap  中 
    if (parentMapping != null) {
      linkToParents(rs, parentMapping, rowValue);
    } else {
      // 回调 resultHandler 来处理结果
      callResultHandler(resultHandler, resultContext, rowValue);
    }
  }

  private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
  	// 获取到父ResultMapping  中该属性的缓存key
    CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getForeignColumn());
    // 获取缓存的对象
    List<PendingRelation> parents = pendingRelations.get(parentKey);
    if (parents != null) {
      for (PendingRelation parent : parents) {
        if (parent != null && rowValue != null) {
          // 将当前对象注入到父级
          linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
        }
      }
    }
  }
  
  private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
    resultContext.nextResultObject(rowValue);
    // 调用ResultHandler#handleResult来处理结果,默认情况是 DefaultResultHandler,将结果保存到 DefaultResultHandler#list 中
    ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
  }

3. handleRowValuesForSimpleResultMap

该方法用来解析非嵌套映射情况,具体实现如下:

  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.offset 属性决定
    skipRows(resultSet, rowBounds);
    // 确定当前剩余数据满足条件,即此次拉取的数据量 < RowBounds.limmit  时 且 连接未关闭 且后续还有结果集,则再次获取
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      // 1. 解析 discriminator 属性
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      // 2. 获取行数据 
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      // 3. 保存映射后的数据
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }
  
  // 跳过指定的行数
  private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
    if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
      if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
        rs.absolute(rowBounds.getOffset());
      }
    } else {
      for (int i = 0; i < rowBounds.getOffset(); i++) {
        if (!rs.next()) {
          break;
        }
      }
    }
  }  
  // 是否应该获取更多列
  private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
  }

这里我们可以看到 :

  1. 利用 RowBounds 是可以实现分页的功能的,但却是一个逻辑分页,因为所有数据都是已经加载到内存后再根据 RowBounds 的分页限制选择是否丢弃或继续获取,因此并不建议使用。

  2. resolveDiscriminatedResultMap 方法实现了对 <discriminator > 标签的解析,并将 <discriminator >解析后的ResultMap 作为最终的 ResultMap 处理,上面已经介绍,不再赘述。

  3. getRowValue 方法会根据 resultMap 解析并获取当前的行数据, 这个跟上面不同是个重载方法,如下:

     // 将数据库查出来的数据转换为  Mapper Interface Method 返回的类型
      private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
        final ResultLoaderMap lazyLoader = new ResultLoaderMap();
        //  1. 反射 Mapper Interface Method 返回的类型对象,这里尚未填充行数据
        Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
        // rowValue 不为空 && 没有针对 rowValue 类型的 TypeHandler 
        if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
          final MetaObject metaObject = configuration.newMetaObject(rowValue);
          boolean foundValues = this.useConstructorMappings;
          // 如果允许自动映射(可通过 <resultMap> 标签的 autoMapping 属性指定)
          if (shouldApplyAutomaticMappings(resultMap, false)) {
          	// 2. 根据自动映射规则尝试映射,看是行数据是否能映射到对应的属性 (忽略大小写的映射)
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
          }
          // 3. 根据属性映射
          foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
          foundValues = lazyLoader.size() > 0 || foundValues;
          // 如果 映射到了属性值 或者 配置了空数据返回实体类 (mybatis.configuration.return-instance-for-empty-row 属性指定)则 返回 rowValue, 否则返回空 
          rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
        }
        // 返回映射后的实体类
        return rowValue;
      }
    
    
  4. storeObject 方法会将处理后的行结果缓存起来。上面已经介绍,这里不再赘述。

至此整个解析过程已经结束。


以上:内容部分参考
https://www.jianshu.com/p/cdb309e2a209
https://zhuanlan.zhihu.com/p/526147349
https://blog.csdn.net/qq_40233503/article/details/94436578
https://blog.csdn.net/weixin_42893085/article/details/105105958
https://blog.csdn.net/weixin_40240756/article/details/108889127
https://www.cnblogs.com/hongshaozi/p/14160328.html
https://www.jianshu.com/p/05f643f27246
https://www.cnblogs.com/sanzao/p/11466496.html
https://juejin.cn/post/6844904127823085581
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/75477.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】C++入门基础详解(1)

本篇内容要分享的是C的基础内容&#xff0c;C的诞生简单的说就是为了填补C语言中的语法坑&#xff0c;同时对比C语言来说增添很多便捷的语法规则&#xff0c;使用起来比C语言便捷不少&#xff0c;但是学习难度也大大增强&#xff0c;不过难度是成线性增长&#xff0c;可以一步一…

WebRTC音视频通话-WebRTC视频自定义RTCVideoCapturer相机

WebRTC音视频通话-WebRTC视频自定义RTCVideoCapturer相机 在之前已经实现了WebRTC调用ossrs服务&#xff0c;实现直播视频通话功能。但是在使用过程中&#xff0c;RTCCameraVideoCapturer类提供的方法不能修改及调节相机的灯光等设置&#xff0c;那就需要自定义RTCVideoCaptur…

应届生运维简历攻略

导语&#xff1a; 当下&#xff0c;计算机科学与技术已经成为一个炙手可热的行业&#xff0c;而作为这个行业中的一份子&#xff0c;运维人员的角色无疑至关重要。如果你是一位即将毕业的应届生&#xff0c;并希望在运维领域打拼&#xff0c;那么一份出色的运维简历将是你踏入…

【hive】hive分桶表的学习

hive分桶表的学习 前言&#xff1a; 每一个表或者分区&#xff0c;hive都可以进一步组织成桶&#xff0c;桶是更细粒度的数据划分&#xff0c;他本质不会改变表或分区的目录组织方式&#xff0c;他会改变数据在文件中的分布方式。 分桶规则&#xff1a; 对分桶字段值进行哈…

CXL registers

目录 DVSEC CXL PCIe DVSEC for CXL Device//ID 0 DVSEC CXL Capability (Offset 0Ah) DVSEC CXL Control (Offset 0Ch) DVSEC CXL Status (Offset 0Eh) DVSEC CXL Control2 (Offset 10h) DVSEC CXL Status2 (Offset 12h) DVSEC CXL Lock (Offset 14h) DVSEC CXL Capabilit…

1€滤波器(1 Euro Filter)使用介绍

怎么调整欧拉角x、y、z的抖动问题&#xff1f;

视频集中存储EasyCVR视频汇聚平台定制项目增加AI智能算法

安防视频集中存储EasyCVR视频汇聚平台&#xff0c;可支持海量视频的轻量化接入与汇聚管理。平台能提供视频存储磁盘阵列、视频监控直播、视频轮播、视频录像、云存储、回放与检索、智能告警、服务器集群、语音对讲、云台控制、电子地图、平台级联、H.265自动转码等功能。为了便…

如何应用项目管理软件进行敏捷开发管理

敏捷开发&#xff08;Agile Development&#xff09;是一种软件开发方法论&#xff0c;强调在不断变化的需求和环境下&#xff0c;通过迭代、协作和自适应的方式来开发软件。敏捷方法的目标是提供更快、更灵活、更高质量的软件交付&#xff0c;以满足客户需求并实现项目成功。 …

使用pymupdf实现PDF内容搜索并显示功能

简介&#xff1a; 在日常工作和学习中&#xff0c;我们可能需要查找和提取PDF文件中的特定内容。本文将介绍如何使用Python编程语言和wxPython图形用户界面库来实现一个简单的PDF内容搜索工具。我们将使用PyMuPDF模块来处理PDF文件&#xff0c;并结合wxPython构建一个用户友好的…

C语言刷题训练【第11天】

大家好&#xff0c;我是纪宁。 今天是C语言笔试刷题训练的第11天&#xff0c;加油&#xff01; 文章目录 1、声明以下变量&#xff0c;则表达式: ch/i (f*d – i) 的结果类型为&#xff08; &#xff09;2、关于代码的说法正确的是&#xff08; &#xff09;3、已知有如下各变…

根据源码,模拟实现 RabbitMQ - 从需求分析到实现核心类(1)

目录 一、需求分析 1.1、对 Message Queue 的认识 1.2、消息队列核心概念 1.3、Broker Server 内部关键概念 1.4、Broker Server 核心 API &#xff08;重点实现&#xff09; 1.5、交换机类型 Direct 直接交换机 Fanout 扇出交换机 Topic 主题交换机 1.6、持久化 1.7…

pytest自动化测试框架tep环境变量、fixtures、用例三者之间的关系

tep是一款测试工具&#xff0c;在pytest测试框架基础上集成了第三方包&#xff0c;提供项目脚手架&#xff0c;帮助以写Python代码方式&#xff0c;快速实现自动化项目落地。 在tep项目中&#xff0c;自动化测试用例都是放到tests目录下的&#xff0c;每个.py文件相互独立&…

分布式图数据库 NebulaGraph v3.6.0 正式发布,强化全文索引能力

本次 v3.6.0 版本&#xff0c;主要强化全文索引能力&#xff0c;以及优化部分场景下的 MATCH 性能。 强化 强化增强全文索引功能&#xff0c;具体 pr 参见&#xff1a;#5567、#5575、#5577、#5580、#5584、#5587 优化 支持使用 MATCH 子句检索 VID 或属性索引时使用变量&am…

【Windows系统编程】02.进程与线程(一)-笔记

进程&#xff0c;进程对象 虚拟内存 进程不能执行代码&#xff0c;数据结构&#xff0c;三环PEB&#xff0c;0怀EPROCESS对进程进行管理 线程列表 线程才是真正执行代码 主线程&#xff1a;主函数 线程依赖于cpu时间片切换 单核&#xff0c;多核 主线程消息&#xff0c…

ChatGPT等人工智能编写文章的内容今后将成为常态

BuzzFeed股价上涨200%可能标志着“转向人工智能”媒体趋势的开始。 周四&#xff0c;一份内部备忘录被华尔街日报透露BuzzFeed正计划使用ChatGPT聊天机器人-风格文本合成技术来自OpenAI&#xff0c;用于创建个性化盘问和将来可能的其他内容。消息传出后&#xff0c;BuzzFeed的…

【数据结构与算法】十大经典排序算法-归并排序

&#x1f31f;个人博客&#xff1a;www.hellocode.top &#x1f3f0;Java知识导航&#xff1a;Java-Navigate &#x1f525;CSDN&#xff1a;HelloCode. &#x1f31e;知乎&#xff1a;HelloCode &#x1f334;掘金&#xff1a;HelloCode ⚡如有问题&#xff0c;欢迎指正&#…

地理数据的双重呈现:GIS与数据可视化

前一篇文章带大家了解了GIS与三维GIS的关系&#xff0c;本文就GIS话题带大家一起探讨一下GIS和数据可视化之间的关系。 GIS&#xff08;地理信息系统&#xff09;和数据可视化在地理信息科学领域扮演着重要的角色&#xff0c;它们之间密切相关且相互增强。GIS是一种用于采集、…

unity新输入系统的简单使用(New InputSystem)

1、在包管理器 unity注册表中下载安装InputSystem 2、给玩家添加组件PlayerInput&#xff0c;点击CreatAction,创建一个InputAct InputAct,这是玩家的输入文件&#xff0c;在里面可以设置玩家输入 3、使用 例如玩家控制角色移动 在InputAct中&#xff0c;默认已经设置好了移…

誉天HCIP-Datacom课程简介

HCIP-Datacom课程介绍&#xff1a;HCIP-Datacom分为一个核心技术方向&#xff1a;HCIP-Datacom-Core Technology H12-821 &#xff08;核心技术&#xff09;六个可选子方向&#xff1a;HCIP-Datacom-Advanced Routing & Switching Technology H12-831 &#xff08;高级路…