这篇文章主要看下mybatis是如何进行结果映射的。
之前提到过,addMapper的过程中,将结果映射的配置保存在了ResultMap中。下面来看下这个类的属性吧。

ResultMap的属性

  private Configuration configuration;//全局配置

  private String id;//唯一标识
  private Class<?> type;//实体类
  private List<ResultMapping> resultMappings;//嵌套查询的相关配置
  private List<ResultMapping> idResultMappings;
  private List<ResultMapping> propertyResultMappings;
  private Set<String> mappedColumns;//嵌套查询的参数字段
  private Set<String> mappedProperties;//嵌套查询的属性名
  private boolean hasNestedResultMaps;//是否含有嵌套的ResultMap
  private boolean hasNestedQueries;//是否含有嵌套的子查询

本篇文章,还是基于那个最简单的查询来分析。因此,讲不到嵌套查询。因此ResultMap中就主要用到id,type这两个属性。以下是mapper.ProductMapper的一个抽象方法:

    @Select("select * from product where id = #{id}")
    Product detail(long id);

这个方法对应的ResultMap的id为"mapper.ProductMapper.detail-long"。type为Product.class。

ResultMap的构建

ResultMap的构建是在addMapper的过程中。代码入口在MapperAnnotationBuilder的parseResultMap方法中。

  private String parseResultMap(Method method) {
    Class<?> returnType = getReturnType(method);//通过反射获取方法返回类型
    ConstructorArgs args = method.getAnnotation(ConstructorArgs.class);//获取ConstructorArgs注解
    Results results = method.getAnnotation(Results.class);//获取Results注解,嵌套查询主要是分析这个注解
    TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
    String resultMapId = generateResultMapName(method);//拼接resultMap的id
    applyResultMap(resultMapId, returnType, argsIf(args), resultsIf(results), typeDiscriminator);//构建ResultMap
    return resultMapId;
  }

可以看到这个过程主要是通过反射来获取的各个字段。我们上面的例子比较简单,没有注解,所以,只有id和type字段有值。正如前面讲的addMapper之后,这个ResultMap便存在了configuration中的resultMaps了。

查询返回值映射

继续上一篇文章,上一篇文章,讲到查询除了结果,剩下DefaultResultSetHandler将结果按照配置映射好。一下为DefaultResultSetHandler的handleResultSet方法。

  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {//为嵌套查询时
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {//没有指定resultHandler则使用默认的
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);//映射数据
          multipleResults.add(defaultResultHandler.getResultList());//拼接结果
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

rsw是将jdbc的返回结果包裹了一层,方便处理。resultMap便是这个查询对应的ResultMap,multipleResults则存放最终结果。parentMapping只有在嵌套查询时才会有值。
可以看到我们的案例parentMapping为null,resultHandle也为null。可以看到映射数据走的是handleRowValues方法。
而handleRowValues中又判断了是否含有嵌套查询,当没有嵌套查询时执行handleRowValuesForSimpleResultMap方法。

  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();//保留上下文
    skipRows(rsw.getResultSet(), rowBounds);//逻辑分页,去掉多余行
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {//遍历数据行
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);//处理Discriminate注解,本文不考虑
      Object rowValue = getRowValue(rsw, discriminatedResultMap);//生成映射对象
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());//将映射对象保存在resultHandler中。
    }
  }

生成映射对象分成了两步,第一步构建映射对象,第二步是为字段赋值。构建映射对象主要用的objectFactory,这个对象保存在configuration中。默认类是DefaultObjectFactory。

对象生成类DefaultObjectFactory

  private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
      Constructor<T> constructor;
      if (constructorArgTypes == null || constructorArgs == null) {//当没有传入构造器参数时
        constructor = type.getDeclaredConstructor();//获取无参构造器
        if (!constructor.isAccessible()) {//保证无参构造器能够访问
          constructor.setAccessible(true);
        }
        return constructor.newInstance();//构建对象并返回
      }
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));//获取参数类型对应的构造器
      if (!constructor.isAccessible()) {
        constructor.setAccessible(true);
      }
      return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));//传入参数构建对象并返回
    } catch (Exception e) {//错误处理,省略
	}
  }

DefaultObjectFactory类主要就是通过这个方法构造出对象的。代码就主要是通过反射。
现在生成了结果对象了,但每个字段都是空的,getRowValue中会调用applyAutomaticMappingst方法将查询结果赋值到各个字段中。

  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);//将每个字段值和对应的属性生成UnMappedColumnAutoMapping对象,并存到list中。
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {//遍历每个UnMappedColumnAutoMapping
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);//获取值
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(mapping.property, value);//将对应的属性填充上值
        }
      }
    }
    return foundValues;
  }

createAutomaticMappings的过程比较繁琐,主要就是遍历结果,将jdbc的字段和对象的属性名对应起来。就不具体看了。最后看看 storeObject是如何将结果保存在resultHandler中的吧。storeObject本质是调用的ResultHandler的handleResult方法。接下来看看DefaultResultHandler类吧。

DefaultResultHandler类

public class DefaultResultHandler implements ResultHandler<Object> {

  private final List<Object> list;//保存最终结果

  public DefaultResultHandler() {
    list = new ArrayList<Object>();
  }

  @SuppressWarnings("unchecked")
  public DefaultResultHandler(ObjectFactory objectFactory) {
    list = objectFactory.create(List.class);
  }

  @Override
  public void handleResult(ResultContext<? extends Object> context) {
    list.add(context.getResultObject());//将上下文中正在处理的对象添加到list中
  }

  public List<Object> getResultList() {
    return list;
  }

}

可以看到默认的ResultHandler非常简单。就是一个list,用add方法将每个结果添加进来。每个结果是通过context.getResultObject()得到的。接下来看下DefaultResultContext类。

public class DefaultResultContext<T> implements ResultContext<T> {

  private T resultObject;//最后一个保存的数据
  private int resultCount;//保存的结果个数
  private boolean stopped;//是否停止

  public DefaultResultContext() {
    resultObject = null;
    resultCount = 0;
    stopped = false;
  }

  @Override
  public T getResultObject() {
    return resultObject;
  }

  @Override
  public int getResultCount() {
    return resultCount;
  }

  @Override
  public boolean isStopped() {
    return stopped;
  }

  public void nextResultObject(T resultObject) {
    resultCount++;
    this.resultObject = resultObject;
  }

  @Override
  public void stop() {
    this.stopped = true;
  }

}

可以看到DefaultResultContext非常简单,只是保存了下单个数据,以及处理过的结果个数。

总结

只考虑这种简单查询的话,生成映射结果的流程并不复杂,最核心的两步就是通过反射生成结果对象、填充各个字段的值。
若考虑嵌套查询的话,可以看到ResultMap中保存了子查询的信息在resultMappings等字段中,可以在里面取出子查询的配置,然后执行子查询的方法,子查询的执行过程其实和父查询都是用之前分析的execute.query方法。有一点类似于递归。
若考虑延迟加载的话,mybatis的延迟加载主要使用:Javassist,Cglib实现

02-26 20:21