MyBatis源码分析之@ResultMap注解详解

Posted 叶长风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis源码分析之@ResultMap注解详解相关的知识,希望对你有一定的参考价值。

MyBatis源码分析之@ResultMap注解详解

在前一篇文章讲**@MapKey注解时,我原想将@ResultMap注解也一起拿出来说一下,但是发现@ResultMap解析加载源码非常多,想想就不在一篇文章中讲了,分开单独来说,这一篇就来彻底探索一下@ResultMap**注解。

1. 加载过程


说到解析Mapper方法上的注解**@ResultMap**,这个就要回到解析configuration中的parseMapper位置了,在mapperRegistry加入当前解析出的mapper时我们知此处不仅做了加载mapper的事情,还进行了非xml方法配置时的加载。

public <T> void addMapper(Class<T> type) 
    mapperRegistry.addMapper(type);
  

在此步addMapper之后,还进行了MapperAnnotationBuilder的解析。

knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();

@ResultMap的解析就在parse方法中,转到parse方法。

public void parse() 
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) 
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) 
        try 
          // issue #237
          if (!method.isBridge()) 
            parseStatement(method);
          
         catch (IncompleteElementException e) 
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        
      
    
    

进入parseStatement(method)方法中。

  void parseStatement(Method method) 
      .....
      String resultMapId = null;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) 
        String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb = new StringBuilder();
        for (String resultMap : resultMaps) 
          if (sb.length() > 0) 
            sb.append(",");
          
          sb.append(resultMap);
        
        resultMapId = sb.toString();
       else if (isSelect) 
        resultMapId = parseResultMap(method);
      

      assistant.addMappedStatement(
          mappedStatementId,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache
          );
  

上述方法中对@ResultMap注解进行了解析,并生成resultMapId,这的操作很有意思,如果ResultMap中需要多种返回的话,@ResultMap中的value是一个数组,可以传多个值进去,然后生成resultMapId时拼接到一起。@ResultMap传入value为多个时写法如下:

@MapKey("id")
    @ResultMap("BaseResultMap", "BaseResultMap2")
    @Select("select * from user where hotel_address = #address;")
    Map<Long, User> getUserByAddress(@Param("address") String address);

那么生成的resultMapId为"BaseResultMap,BaseResultMap2"。这倒是挺有意思,但是我一般也没见过这样写的,找个时间我尝试了,有什么特别地方的话我就回来补充到这下面。

在生成resultMapId后将其他参数一起生成MappedStatement对象并保存进mappedStatements中。

public void addMappedStatement(MappedStatement ms) 
    mappedStatements.put(ms.getId(), ms);
  

这里使用的key也就是MappedStatement的id,也就是我们在之前文章中说到的id是用当前类名加方法名组装而成的,具体过程在之前的parseStatement中。

void parseStatement(Method method) 
      Options options = method.getAnnotation(Options.class);
      final String mappedStatementId = type.getName() + "." + method.getName();

我原想分析到这就完了,但是这里面仅仅只是得到了resultMapId字段,但是在后面使用的时候实际上是直接取出了整个ResultMap映射关系,所以还要继续看上述parseStatement方法中的assistant.addMappedStatement方法。

public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) 

    if (unresolvedCacheRef) 
      throw new IncompleteElementException("Cache-ref not yet resolved");
    

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) 
      statementBuilder.parameterMap(statementParameterMap);
    

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  

addMappedStatement还有一个操作getStatementResultMaps(resultMap, resultType, id),这一步用来获取resultMaps集合。

private List<ResultMap> getStatementResultMaps(
      String resultMap,
      Class<?> resultType,
      String statementId) 
    resultMap = applyCurrentNamespace(resultMap, true);

    List<ResultMap> resultMaps = new ArrayList<ResultMap>();
    if (resultMap != null) 
      String[] resultMapNames = resultMap.split(",");
      for (String resultMapName : resultMapNames) 
        try 
          resultMaps.add(configuration.getResultMap(resultMapName.trim()));
         catch (IllegalArgumentException e) 
          throw new IncompleteElementException("Could not find result map " + resultMapName, e);
        
      
     else if (resultType != null) 
      ResultMap inlineResultMap = new ResultMap.Builder(
          configuration,
          statementId + "-Inline",
          resultType,
          new ArrayList<ResultMapping>(),
          null).build();
      resultMaps.add(inlineResultMap);
    
    return resultMaps;
  

这一步中有两个操作需要关注一下,一个是resultMap对象是从configuration.getResultMap(resultMapName.trim())中取出来的,而configuration中的resultMap是在解析xml时解析ResultMap节点从而初始化的。这一步完结,还有一步比较关键的是在构造ResultMap时,最后一个字段赋值为null,而这个字段名为autoMapping,这个比较重要,在后文映射字段值时需要用到,这个到时再说。

2. 字段映射


在解析完@ResultMap之后,现在就是在查询完之后字段映射时候发挥作用了,在session查询中找到selectList查询方法,继续追踪到Executor的query方法,最终到SimpleStatementHandler中的query方法。

不过在直接看query方法之前还要再回头来看下selectList方法。

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) 
    try 
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
     catch (Exception e) 
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
     finally 
      ErrorContext.instance().reset();
    
  

这一步有个操作就是获取到MappedStatement对象, 从许多前文中我们知MappedStatement对象中存放着Sql、ResultMap、timeout等等参数,而在后文中就需要从MappedStatement对象中取出ResultMap中,这个等会再说,先看query方法。

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException 
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
  

在statement执行完query方法后,剩下的就是处理结果集以我们想要的形式返回,这一步的处理在handleResultSets中。

@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 rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) 
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    

    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);
  

这里就要说到前文中的MappedStatement对象了,这里取出了ResultMap集合,然后在遍历rsw中,对rsw记录与resultMap中字段进行映射,进入到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) 
          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());
    
  

处理每一行的数据在handleRowValues方法中,进入handleRowValues方法中。

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException 
    if (resultMap.hasNestedResultMaps()) 
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
     else 
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    
  

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);
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    
  

最后的结果处理在getRowValue中,这里返回的Object对象其实就是对应查询出来的对象类了,在这就是user对象,getRowValue应该就是对具体的字段进行映射与赋值了,还是进去看一下吧。

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException 
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) 
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      if (shouldApplyAutomaticMappings(resultMap, false)) 
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    
    return rowValue;
  

createResultObject这一步实质就是根据类型创建返回对象,如果可以自动映射关系的话,就在applyAutomaticMappings(rsw, resultMap, metaObject, null)这一步进行字段映射,可以进这个方法中看下是如何进行一步步映射字段value的。

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);以上是关于MyBatis源码分析之@ResultMap注解详解的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis ResultMap复合映射使用以及源码分析

MyBatis注解@Select@Update分析

SpringBoot+MyBatis中自动根据@Table注解和@Column注解生成ResultMap

mybatis 注解@Results@Result@ResultMap@One的使用

MyBatis注解开发的两种方法@Results和resultMap

Mybatis源码分析之Select返回数据分析