1. 程式人生 > >MyBatis原始碼分析之@ResultMap註解詳解

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);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)
            
           

相關推薦

MyBatis原始碼分析@ResultMap註解

MyBatis原始碼分析之@ResultMap註解詳解 在前一篇文章講**@MapKey註解時,我原想將@ResultMap註解也一起拿出來說一下,但是發現@ResultMap解析載入原始碼非常多,想想就不在一篇文章中講了,分開單獨來說,這一篇就來徹底探索一下@ResultMap**

MyBatis原始碼分析@SelectProvider註解使用

MyBatis原始碼分析之@SelectProvider註解使用詳解 之前講了MyBatis的配置、plugin、Select查詢,還有@MapKey註解的使用與原理,還有返回@ResultMap等等,我原想直接從MyBatis的快取開始說起,但是想想還是得說一下MyBatis中的@

AFNetworking3.1.0原始碼分析(四)AFHTTPRequestSerializer 初始化方法

1:類圖介紹 在AFHTTPSessionManager 初始化方法中可以看到 AFNetworking 預設使用的網路請求序列化類是AFHTTPRequestSerializer,一下是關於它的類圖: 2:類功能分析:  一:初始化函式: - (instancetyp

死磕 java併發包AtomicStampedReference原始碼分析(ABA問題

問題 (1)什麼是ABA? (2)ABA的危害? (3)ABA的解決方法? (4)AtomicStampedReference是什麼? (5)AtomicStampedReference是怎麼解決ABA的? 簡介 AtomicStampedReference是java併發包下提供的一個原子類,它能解決其它原子

linux逆向分析ELF檔案

前言 首先如果大家遇到ELF二進位制檔案的逆向首先考慮的可能就是通過IDA進行靜態逆向分析演算法,那麼我們首先就要了解ELF(Executable and Linking Format)的檔案格式。 ELF檔案格式主要分為以下幾類: 1. 可重定位檔案(Relocatable File),這類檔

MyBatis原始碼分析抽象工廠模式和建造者模式的應用

抽象工廠模式的應用 MyBatis原始碼的註釋不多,不過SqlSession倒是給了兩行註釋: /** * The primary Java interface for working with MyBatis. * Through this inter

Mybatis原始碼分析Select返回資料分析

Mybatis原始碼分析之Select返回資料分析 在之前的一篇文章中分析了@Select註解的使用方法,在查詢方法中我們知可以返回Map型別,也可以返回指標,或者是list集合,或是單條記錄,今天就對這幾種返回做一個原始碼分析。 Select查詢 在這裡就不需要再寫一

Mybatis原始碼分析結果封裝ResultSetHandler和DefaultResultSetHandler

(2)處理儲存過程執行後的輸出引數ResultSetHandler是一個介面,提供了兩個函式分別用來處理普通操作和儲存過程的結果,原始碼如下:/** * @author Clinton Begin */ public interface ResultSetHandler { <E> Li

MapReduce階段原始碼分析以及shuffle過程

MapReducer工作流程圖: 1. MapReduce階段原始碼分析 1)客戶端提交原始碼分析 解釋:   - 判斷是否列印日誌   - 判斷是否使用新的API,檢查連線   - 在檢查連線時,檢查輸入輸出路徑,計算切片,將jar、配置檔案複製到HDFS   - 計算切片時,計算最小切片數(預設為1

Mybatis原始碼分析儲存過程呼叫

這一篇部落格我們學習一下Mybatis呼叫儲存過程的使用和執行流程。首先我們先建立一個簡單的儲存過程DELIMITER $ CREATE PROCEDURE mybatis.ges_user_count(IN age INT, OUT user_count INT) BEGI

OLAP多維分析Mondrian Schema

該文章看後感覺對與如何使用PSW工具定義Schema模型更為清楚,很受用是好東西。 Schema Schema 定義了一個多維資料庫。包含了一個邏輯模型,而這個邏輯模型的目的是為了書寫 MDX 語言的查詢語句。 這個邏輯模型實際上提供了這幾個概念: Cubes (立方體)

Spark Streaming原始碼解讀No Receivers

背景: 目前No Receivers在企業中使用的越來越多。No Receivers具有更強的控制度,語義一致性。No Receivers是我們操作資料來源自然方式,操作資料來源使用一個封裝器,且是RDD型別的。所以Spark Streaming就產生了自定義

Mybatis原始碼分析Spring與Mybatis整合MapperScannerConfigurer處理過程原始碼分析

        前面文章分析了這麼多關於Mybatis原始碼解析,但是我們最終使用的卻不是以前面文章的方式,編寫自己mybatis_config.xml,而是最終將配置融合在spring的配置檔案中。有了前面幾篇部落格的分析,相信這裡會容易理解些關於Mybatis的初始化及

Mybatis原始碼分析引數對映及處理ParameterHandler

ParameterHandler是用來設定引數規則的,當StatementHandler呼叫prepare方法之後,接下來就是呼叫它來進行設定引數。ParameterHandler介面:public interface ParameterHandler { Object

複習MyBatis基礎用法(二)——ResultMap用法

ResultMap – 是最複雜也是最強大的元素 它就是讓你遠離 90%的需要從結果集中取出資料的 JDBC 程式碼的那個東西, 而且在一些情形下允許你做一些 JDBC 不支援的事情。事實上, 編寫相似於對複雜語句聯合對映這些等同的程式碼, 也許可以跨過上千行的

netty原始碼分析-SimpleChannelInboundHandler與ChannelInboundHandlerAdapter(6)

每一個Handler都一定會處理出站或者入站(也可能兩者都處理)資料,例如對於入站的Handler可能會繼承SimpleChannelInboundHandler或者ChannelInboundHandlerAdapter,而SimpleChannelIn

死磕Netty原始碼記憶體分配(二)PoolArena記憶體分配結構分析

前言 在應用層通過設定PooledByteBufAllocator來執行ByteBuf的分配,但是最終的記憶體分配工作被委託給PoolArena。由於Netty通常用於高併發系統所以各個執行緒進行記憶體分配時競爭不可避免,這可能會極大的影響記憶體分配的效率,為

spring原始碼分析spring-jdbc模組

0 概述 Spring將替我們完成所有使用JDBC API進行開發的單調乏味的、底層細節處理工作。下表描述了哪些是spring幫助我們做好的,哪些是我們要做的。 Action  Spring  You Define connection parameters.  X

jQuery原始碼分析jQuery(selector,context)

首先我們給出下面的HTML程式碼: <div id="parent" class="parent"> <div class="child"> child1 </div> <div class="child">

spring原始碼分析spring-messaging模組

0 概述 spring-messaging模組為整合messaging api和訊息協議提供支援。 其程式碼結構為: 其中base定義了訊息Message(MessageHeader和body)、訊息處理MessageHandler、傳送訊息MessageChann