淺析mybatis源碼(五)結果映射

這篇文章主要看下mybatis是如何進行結果映射的。
以前提到過,addMapper的過程當中,將結果映射的配置保存在了ResultMap中。下面來看下這個類的屬性吧。java

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的一個抽象方法:mybatis

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

這個方法對應的ResultMap的id爲"mapper.ProductMapper.detail-long"。type爲Product.class。app

ResultMap的構建

ResultMap的構建是在addMapper的過程當中。代碼入口在MapperAnnotationBuilder的parseResultMap方法中。ide

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了。ui

查詢返回值映射

繼續上一篇文章,上一篇文章,講到查詢除告終果,剩下DefaultResultSetHandler將結果按照配置映射好。一下爲DefaultResultSetHandler的handleResultSet方法。this

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方法。code

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類吧。ip

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實現

相關文章
相關標籤/搜索