Mybatis 源码 ④ :TypeHandler

news2024/12/27 11:03:09

文章目录

  • 一、前言
  • 二、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
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

git一次错误提交的回滚(不同分支因merge-需回滚)

场景&#xff1a;提交到sit的代码&#xff0c;结果解决冲突merge了DEV的代码&#xff0c;所以要回滚到合并之前的代码 &#xff08;原因是我再网页上处理了冲突&#xff0c;他就自动merge了,如图—所以还是idea处理冲突&#xff0c;可控&#xff09; 方式二&#xff1a; &…

【网络基础实战之路】基于BGP协议中的联邦号连接三个AS区域的实战详解

系列文章传送门&#xff1a; 【网络基础实战之路】设计网络划分的实战详解 【网络基础实战之路】一文弄懂TCP的三次握手与四次断开 【网络基础实战之路】基于MGRE多点协议的实战详解 【网络基础实战之路】基于OSPF协议建立两个MGRE网络的实验详解 【网络基础实战之路】基于…

Windows CMD 关闭,启动程序

Windows CMD 关闭&#xff0c;启动程序 1. Windows 通过 CMD 命令行关闭程序 示例&#xff1a;通过 taskkill 命令关闭 QQ 管家&#xff0c;但是这里有个问题&#xff0c;使用命令行关闭 QQ 管家时&#xff0c;会提示“错误: 无法终止 PID 1400 (属于 PID 22116 子进程)的进程…

CAR-T攻克实体瘤发展现状及未来挑战

CAR-T细胞疗法已经成为肿瘤治疗领域的一项重要新兴治疗方法。CAR-T&#xff08;Chimeric Antigen Receptor T-cell&#xff09;细胞疗法利用改造的T细胞来攻击患者体内的肿瘤细胞&#xff0c;具有针对性强、治疗效果显著等特点。 目前&#xff0c;在血液瘤领域&#xff0c;CAR-…

【vue】点击按钮弹出卡片,点击卡片中的取消按钮取消弹出的卡片(附代码)

实现思路&#xff1a; 在按钮上绑定一个点击事件&#xff0c;默认是true&#xff1b;在export default { }中注册变量给卡片标签用v-if判断是否要显示卡片&#xff0c;ture则显示&#xff1b;在卡片里面写好你想要展示的数据&#xff1b;给卡片添加一个取消按钮&#xff0c;绑…

【Unity】ShaderGraph应用(模型膨胀流动)

【Unity】ShaderGraph应用&#xff08;模型膨胀流动&#xff09; 实现效果 ShaderGraph是 unity的图形化 Shader 编程工具。本文介绍使用ShaderGraph实现模型的膨胀流动效果。该效果可以由于模拟流体在管线中的流动等相关功能。 一、实现的方法 1.使用节点介绍 关键节点 UV…

分类预测 | MATLAB实现BO-BiGRU贝叶斯优化双向门控循环单元多输入分类预测

分类预测 | MATLAB实现BO-BiGRU贝叶斯优化双向门控循环单元多输入分类预测 目录 分类预测 | MATLAB实现BO-BiGRU贝叶斯优化双向门控循环单元多输入分类预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.Matlab实现BO-BiGRU贝叶斯优化双向门控循环单元多特征分…

页面切换后,滚动栏问题

项目场景&#xff1a; 提示&#xff1a;react项目antd后台管理系统 问题描述 后台管理系统从a页面进入b页面&#xff0c;a页面有数据&#xff08;有滚动条&#xff0c;且scollTop大于0&#xff09;&#xff0c;进入b页面后&#xff0c;滚动条不是位于初始位置&#xff08;scol…

与Docker同行:Tomcat安装指南

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; 与Docker同行&#xff1a;Tomcat安装指南 ⏱️ 创作时间&#xff1a; …

小龟带你妙写排序之插入排序

插入排序 一. 插入排序的原理二. 题目三. 思路分析(图加文字)四. 代码演示 一. 插入排序的原理 在前N个有序的元素&#xff0c;从N个元素之后全是无序的元素&#xff0c;将无序的元素插入到有序的元素中&#xff0c;形成一个有序的数组 二. 题目 给定一个有N个元素的数组&#…

Flowable初体验

创建一个普通Maven项目 目录结构 一、依赖、配置 pom.xml <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apac…

动力节点|深入浅出Vue框架学习教程,带你快速掌握前端开发核心技能

Vue是一款流行的JavaScript前端框架&#xff0c;最初由华人开发者尤雨溪创建&#xff0c;并在GitHub上开源发布&#xff0c;它采用MVVM模型的设计思维&#xff0c;专注于UI项目的开发&#xff0c;能够方便地组织和管理页面上的各个组件&#xff0c;大大提高了前端开发的效率。 …

基于Java+SpringBoot+vue前后端分离卓越导师双选系统设计实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

通讯协议039——全网独有的OPC HDA知识一之聚合(七)实际时间最大值

本文简单介绍OPC HDA规范的基本概念&#xff0c;更多通信资源请登录网信智汇(wangxinzhihui.com)。 本节旨在详细说明HDA聚合的要求和性能。其目的是使HDA聚合标准化&#xff0c;以便HDA客户端能够可靠地预测聚合计算的结果并理解其含义。如果用户需要聚合中的自定义功能&…

Win10使用Guest和空密码访问共享的完整步骤

目录 前言 启动Guest 给予Guest网络权限 允许空密码登陆 启用不安全的来并登陆 总结 前言 我们经常需要使用空密码和guest账户访问Windows共享&#xff0c;因为某些设备不支持输入密码等&#xff0c;那么该如何设置呢&#xff0c;因为步骤比较固定而且繁琐&#xff0c;于…

IDEA常用设置与maven项目部署

目录 前言 一、Idea是什么 二、Idea的优点 三、Idea的常用设置 主题设置 设置鼠标悬浮提示 忽略大小写提示 自动导包 取消单行显示Tabs 设置字体 配置类文档注释信息模版 设置文件编码 设置自动编译 水平或者垂直显示代码 快捷方式改成eclipse 设置默认浏览器…

世微 AP5160 DC-DC降压恒流IC 大功率LED电源手电筒车灯驱动芯片 SOT23-6

产品描述 AP5160 是一款效率高&#xff0c;稳定可靠的 LED 灯恒流驱动芯片&#xff0c;内置高精度比较器&#xff0c;固定关断时 间控制电路&#xff0c;恒流驱动电路等&#xff0c;特别适合大功率 LED 恒流驱动。 AP5160 采用SOT23-6 封装&#xff0c;通过调节外置电流检测电…

电脑运行慢怎么办?四招教你优化速度

电脑运行慢怎么办&#xff1f;电脑运行缓慢是很多人常遇到的问题&#xff0c;而其中一个主要原因是电脑内堆积了过多的垃圾文件。因此&#xff0c;定期清理电脑垃圾是保持电脑流畅运行的重要步骤。 本文将介绍四种有效的方法&#xff0c;帮助你清理电脑垃圾&#xff0c;让电脑…

如何从cpu改为gpu,pytorch,cuda

1.cmd输入nvcc -V 2.得到 cuda版本后&#xff0c;去pytorch官网 3.根据自己的cuda进行选择 4.复制上述链接&#xff0c;进入cmd 5.cmd中输入activate XXX,这里的"XXX"指代自己在工程中用到的环境 6.进入后&#xff0c;将刚才链接粘贴&#xff0c;回车等待下载结束 …

java 解析p12证书 (获取公钥、私钥、序列号)

1. p12文件放在resources目录下 2. P12InfoVo import lombok.Builder; import lombok.Data;import java.io.Serializable; import java.security.PrivateKey; import java.security.PublicKey;/*** p12证书VO*/ Data Builder public class P12InfoVo implements Serializable{/…