MyBatis源码学习—MyBatis结果处理器ResultSetHandler详解

上一篇博文MyBatis源码学习—MyBatis数据仓库会话器StatementHandler详解我们通过Statement将SQL发送到了数据库,并返回了ResultSet,接下来就是将结果集 ResultSet 自动映射成实体类对象。这样使用者就无需再手动操作结果集,并将数据填充到实体类对象中。这可大大降低开发的工作量,提高工作效率。

1、ResultSetHandler 简介

回想一下,一条 SQL 的请求在MyBatis中会经过哪几个步骤?

(1)首先会经过Executor,一般默认的配置是SimpleExecutor,Eexcutor负责创建并维护一个StatementHandler对象,之后直接调用StatementHandler中的增删改查方法即可;

(2)StatementHandler有三种类型的:简单类型的SimpleStatementHandler、预编译SQL类型的PrepraedStatementHandler以及执行存储过程的CallabletatenmentHandler,他们内部分别维护了一个Statement对象、PrepraedStatementHandler对象以及CallableStatementHandler对象,另外在ParameterStatementHandler和CallabletatenmentHandler还维护一个ParameterHandler对象用于解析参数。说白了,MyBatis最终还是通过JDBC中的Statement对象、PrepraedStatement对象以及CallableStatement对象和数据库交互的。

(3)查询数据库返回结果之后就需要把查询的结果通过某个东东映射成为实体类对象并且以Mapper方法指定的类型返回结果集,这个东东就是今天我们要了解的神器**ResultSetHandler **

下图所示就是ResultSetHnalder接口的方法。

ResultSetHandler接口,和ParameterHandler一样,ResultSetHandler也只有一个默认实现类DefaultResultSetHandler

2、ResultSetHandler 对象的创建

ResultSetHandler 对象是在创建 StatementHandler 对象的同时被创建,由 Configuration 对象负责创建,以BaseStatementHandler为例

具体创建逻辑在Configuration类中的newResiltSetHandler方法中,如下图所示。

可以看到,关键过程非常简单,就是直接new了一个默认ResultSetHandler 实现类DefaultResultSetHandler

3、ResultSetHandler 处理结果映射

(1)映射结果处理过程

我们来看看上次看源码的位置,如下图所示源码就是SimpleStatementHandler中query方法源码。

在StatementHanlder借助Statement查询到结果后,直接调用了ResultSetHandler的handleResultSets方法来处理返回的结果集。从上面的源码可以看到,在StatementHandler中还维护了一个ResultSetHandler对象。上面我说到过,ResultSetHandler只有一个默认实现类DefaultResultSetHandler,ResultSetHandler 主要负责处理两件事:

  1. 处理 Statement 执行后产生的结果集,生成结果列表
  2. 处理存储过程执行后的输出参数

按照 Mapper 文件中配置的 ResultType 或 ResultMap 来封装成对应的对象,最后将封装的对象返回即可。

来看一下主要的源码:

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

  final List<Object> multipleResults = new ArrayList<Object>();

  int resultSetCount = 0;
  // 获取结果集,ResultSetWrapper就是对ResultSet的包装
  ResultSetWrapper rsw = getFirstResultSet(stmt);
  // 获取结果映射
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  // 结果映射的大小
  int resultMapCount = resultMaps.size();
  // 校验结果映射的数量
  validateResultMapsCount(rsw, resultMapCount);
  // 如果ResultSet 包装器不是null, 并且 resultmap 的数量  >  resultSet 的数量的话
  // 因为 resultSetCount 第一次肯定是0,所以直接判断 ResultSetWrapper 是否为 0 即可
  while (rsw != null && resultMapCount > resultSetCount) {
    // 从 resultMap 中取出 resultSet 数量
    ResultMap resultMap = resultMaps.get(resultSetCount);
    // 处理结果集, 关闭结果集
    handleResultSet(rsw, resultMap, multipleResults, null);
    rsw = getNextResultSet(stmt);
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }

  // 下面逻辑是处理多结果集的。首先从 mappedStatement 取出多个结果集
  String[] resultSets = mappedStatement.getResultSets();
  if (resultSets != null) {
    while (rsw != null && resultSetCount < resultSets.length) {
      ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
      if (parentMapping != null) {
        String nestedResultMapId = parentMapping.getNestedResultMapId();
        ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
        handleResultSet(rsw, resultMap, null, parentMapping);
      }
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
  }

  return collapseSingleResultList(multipleResults);
}

在实际运行过程中,通常情况下一个Sql语句只返回一个结果集,对多个结果集的情况不做分析 。实际很少用到。继续看handleResultSet方法

通过handleRowValues 映射ResultSet结果,最后映射的结果会在defaultResultHandler的ResultList集合中,最后将结果加入到multipleResults中就可以返回了,我们继续跟进handleRowValues这个核心方法

我们可以通过resultMap.hasNestedResultMaps()判断查询语句是否是嵌套查询,如果resultMap中包含且其select属性不为空,则为嵌套查询。本文先分析简单的映射:

(2)ResultList

MyBatis 默认提供了 RowBounds 用于分页,从上面的代码中可以看出,这并非是一个高效的分页方式,是查出所有的数据,进行内存分页。除了使用 RowBounds,我们可以使用一些第三方分页插件进行分页,比如PageHelper。

重要的逻辑已经注释出来了。分别如下:

  1. 创建实体类对象
  2. 自动映射结果集中有的column,但resultMap中并没有配置
  3. 根据 节点中配置的映射关系进行映射

创建实体类对象

说明:创建代理类,默认使用 Javassist 框架生成代理类,但是深入到createProxy方法中最终会发现是通过CGLib实现创建代理对象的。

createResultObject 重载方法的逻辑:

一般情况下,MyBatis 会通过 ObjectFactory 调用默认构造方法创建实体类对象。看看是如何创建的

很简单,就是通过反射创建对象

(3)结果集映射

映射结果集分为两种情况:

  • 一种是自动映射(结果集有但在resultMap里没有配置的字段),在实际应用中,都会使用自动映射,减少配置的工作。自动映射在Mybatis中也是默认开启的。
  • 第二种是映射ResultMap中配置的,这两中映射我们分开来看

自动映射

自动映射就是MyBatis根据实体类中的字段,把查询的结果自动的映射到字段属性上,然后封装成一个对象返回数据。

首先是获取 **UnMappedColumnAutoMapping **集合,然后遍历该集合,并通过 TypeHandler 从结果集中获取数据,最后再将获取到的数据设置到实体类对象中。

UnMappedColumnAutoMapping是DefaultResultSetHandler的内部类, 用于记录没有在配置在 节点中的映射关系。它的代码如下:

UnMappedColumnAutoMapping仅仅就是用于记录一些未在resultMap标签中配置的自动映射行(即实体类中的属性)。下面看一下获取 UnMappedColumnAutoMapping 集合的过程,如下:

private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {

    final String mapKey = resultMap.getId() + ":" + columnPrefix;
    // 从缓存中获取 UnMappedColumnAutoMapping 列表
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
    // 缓存未命中
    if (autoMapping == null) {
        autoMapping = new ArrayList<UnMappedColumnAutoMapping>();
        // 从 ResultSetWrapper 中获取未配置在 <resultMap> 中的列名
        final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
        for (String columnName : unmappedColumnNames) {
            String propertyName = columnName;
            if (columnPrefix != null && !columnPrefix.isEmpty()) {
                if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
                    propertyName = columnName.substring(columnPrefix.length());
                } else {
                    continue;
                }
            }
            // 将下划线形式的列名转成驼峰式,比如 AUTHOR_NAME -> authorName
            final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
            if (property != null && metaObject.hasSetter(property)) {
                // 检测当前属性是否存在于 resultMap 中
                if (resultMap.getMappedProperties().contains(property)) {
                    continue;
                }
                // 获取属性对于的类型
                final Class<?> propertyType = metaObject.getSetterType(property);
                if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
                    final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
                    // 封装上面获取到的信息到 UnMappedColumnAutoMapping 对象中
                    autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
                } else {
                    configuration.getAutoMappingUnknownColumnBehavior()
                        .doAction(mappedStatement, columnName, property, propertyType);
                }
            } else {
                configuration.getAutoMappingUnknownColumnBehavior()
                    .doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
            }
        }
        // 写入缓存
        autoMappingsCache.put(mapKey, autoMapping);
    }
    return autoMapping;
}

先来看看从**ResultSetWrapper **中获取未配置在 中的列名

来看看那loadMappedAndUnmappedColumnNames方法是如何加载列名的。

首先是从当前数据集中获取列名集合,然后获取 中配置的列名集合。之后遍历数据集中的列名集合,并判断列名是否被配置在了 节点中。若配置了,则表明该列名已有映射关系,此时该列名存入 mappedColumnNames 中。若未配置,则表明列名未与实体类的某个字段形成映射关系,此时该列名存入 unmappedColumnNames 中。

映射result节点

接下来分析一下 MyBatis 是如何将结果集中的数据填充到已配置ResultMap映射的实体类字段中的。

private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    
    // 获取已映射的列名
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    // 获取 ResultMapping集合
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    // 遍历所有的ResultMapping进行映射
    for (ResultMapping propertyMapping : propertyMappings) {
        String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
        if (propertyMapping.getNestedResultMapId() != null) {
            column = null;
        }
        if (propertyMapping.isCompositeResult()
            || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
            || propertyMapping.getResultSet() != null) {
            
            // 从结果集中获取指定列的数据
            Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
            
            final String property = propertyMapping.getProperty();
            if (property == null) {
                continue;

            // 若获取到的值为 DEFERED,则延迟加载该值
            } else if (value == DEFERED) {
                foundValues = true;
                continue;
            }
            if (value != null) {
                foundValues = true;
            }
            if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
                // 将获取到的值设置到实体类对象中
                metaObject.setValue(property, value);
            }
        }
    }
    return foundValues;
}

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);
        return DEFERED;
    } else {
        final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
        final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
        // 从 ResultSet 中获取指定列的值
        return typeHandler.getResult(rs, column);
    }
}

从 ResultMap 获取映射对象 ResultMapping 集合。然后遍历 ResultMapping 集合,再此过程中调用 getPropertyMappingValue 获取指定指定列的数据,最后将获取到的数据设置到实体类对象中。

这里和自动映射有一点不同,自动映射是从直接从ResultSet 中获取指定列的值,但是通过ResultMap多了一种情况,那就是关联查询,也可以说是延迟查询,此关联查询如果没有配置延迟加载,那么就要获取关联查询的值,如果配置了延迟加载,则返回DEFERED

参考资料

留言区

还能输入500个字符