1. 程式人生 > >MyBatis註解@Select、@Update分析

MyBatis註解@Select、@Update分析

MyBatis註解@Select、@Update分析

前面幾篇文章分別分析了Mybatis中的Configuration的配置資訊,MyBatis中的Mapper呼叫等等,在分析配置資訊時只是講了如何解析xml中的sql查詢,但是並沒有講怎麼解析Mapper中註解對應的SQL,就是如下:

@ResultMap("BaseResultMap")
@Select("select id, username, password, age, mobile, hotel_address from user where id=#{id};")
User getUser2(@Param("id"
) Long id);

這一節就是具體來說如何解析註解,並將其儲存進mappedStatements中。

1. 解析configuration


我們回到解析mapper的原始碼所在,為mapperElement(root.evalNode(“mappers”));在mapperElement方法中,無論是哪一種條件,最終會有一個addMapper的操作,所以這裡直接看addMapper操作即可,我們轉到最終的addMapper方法。

 public <T> void addMapper(Class<T> type) {
    if (type.
isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { 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(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }

這個方法中不僅僅做了 knownMappers.put(type, new MapperProxyFactory(type));操作,同時進行了MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);對於註解提供的方法就是在這裡開始解析的,我們進入到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));
        }
      }
    }
    parsePendingMethods();
  }

進入到parseStatement中,檢視解析註釋細節,如下:

  void parseStatement(Method method) {
   final String mappedStatementId = type.getName() + "." + method.getName();
    //獲取到具體SQL
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
      //獲取到resultMap
      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);
      }
	  //將對應sqlSource以及其他引數儲存進Map中
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method));
    }
  }

這裡根據Mapper所在的包名加上對應解析的方法名,最終組裝成mappedStatementId,也就是最終儲存進map中的key。

在組裝完對應的key、value sqlSource等引數後,呼叫addMappedStatement進行進一步的包裝,再看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;
  }

這個方法中在經過將cache、timeout、sql、resource、fetchSize等等引數進行包裝後生成MappedStatement物件,最後通過configuration.addMappedStatement(statement)方法,將statement儲存進mappedStatements Map中。

對註解形式提供的SQL的分析就到此為止了。