MyBatis公式チュートリアルおよびソースコード解析-mapperマッピングファイル

26778 ワード

キャッシュ
1.公式文書
MyBatisには、構成とカスタマイズを容易にする強力なクエリーキャッシュ機能が含まれています.MyBatis 3のキャッシュ実装の多くの改良は、より強力で構成しやすいように実現されています.
デフォルトではキャッシュがオンになっていません.ローカルのsessionキャッシュに加えて、変現を強化し、ループ依存を処理する必要があります.2次キャッシュを開くには、SQLマッピングファイルに行を追加する必要があります.
<cache/>

文字通りそうです.この単純な文の効果は次のとおりです.
・マッピング文ファイル内のすべてのselect文がキャッシュされます.
・文ファイル内のすべてのinsertをマッピングすると、update文とdelete文がキャッシュをリフレッシュします.
・キャッシュは、Least Recently Used(LRU、最近最も使用されていない)アルゴリズムを使用して回収される.
・キャッシュは、リフレッシュ間隔なしのno Flush Intervalなどのスケジュールに従って、任意の時間順序でリフレッシュされません.
・キャッシュは、クエリ・メソッドが何を返すかにかかわらず、リスト・セットまたはオブジェクトの1024個の参照を格納する.
・キャッシュはread/write(読み取り/書き込み可能)のキャッシュと見なされ、オブジェクト検索が共有されていないことを意味し、他の呼び出し者またはスレッドが行う潜在的な変更に干渉することなく、呼び出し者によって安全に変更されることを意味する.
これらのアトリビュートはすべて、キャッシュ要素のアトリビュートで変更できます.例:
<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

このより高度な構成では、FIFOキャッシュが作成され、60秒ごとにリフレッシュされ、結果オブジェクトまたはリストの512個の参照が格納され、返されるオブジェクトは読み取り専用とみなされるため、異なるスレッド内の呼び出し者間で変更すると競合します.
利用可能な回収ポリシーは次のとおりです.
・LRU–最も最近使用されていないオブジェクトを削除します.
・FIFO–オブジェクトがキャッシュに入る順に削除します.
・SOFT–ソフトリファレンス:ゴミ回収器の状態とソフトリファレンス規則に基づくオブジェクトを除去します.
・WEAK–弱参照:ゴミ収集器の状態と弱参照規則に基づくオブジェクトをより積極的に除去する.
デフォルトはLRUです.
flushInterval(リフレッシュ間隔)は、任意の正の整数に設定でき、合理的なミリ秒形式の期間を表す.デフォルトでは、リフレッシュ間隔が設定されていない、つまり、キャッシュが文のみを呼び出すときにリフレッシュされます.
size(参照数)は、キャッシュされたオブジェクトの数と実行環境で使用可能なメモリリソースの数を記憶する任意の正の整数に設定できます.既定値は1024です.
readOnly(読み取り専用)アトリビュートはtrueまたはfalseに設定できます.読み取り専用キャッシュは、すべての呼び出し元にキャッシュオブジェクトの同じインスタンスを返します.したがって、これらのオブジェクトは変更できません.これにより、重要なパフォーマンスのメリットが得られます.読み書き可能なキャッシュは、キャッシュ・オブジェクトのコピー(シーケンス化)を返します.これは遅くなりますが、安全なので、デフォルトはfalseです.
2.ソース解析
キャッシュの解析は比較的簡単で、mybatisは構成に基づいてcacheオブジェクトを生成し、configurationに格納します.各マッピングプロファイルには1つのcacheしかありません.複数を構成する場合は1つ目のみ有効です.
//XMLMapperBuilder 
private void cacheElement(XNode context) throws Exception {
if (context != null) {
      //    
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      // builderAssistant    
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }
//MapperBuilderAssistant 
 public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
// cache  configuration
configuration.addCache(cache);
//    cache
    currentCache = cache;
    return cache;
  }

新しいキャッシュを定義するだけでなく、cache-refを利用して他のマッピングファイル構成のキャッシュを直接使用することもできます.
<cache-ref namespace="com.someone.application.data.SomeMapper"/>

の解析はの前にあるので、currentCacheオブジェクトは後者を選択します.
private void cacheRefElement(XNode context) {
if (context != null) {
      //              configuration
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
//       cacheRefResolver   ,       MapperBuilderAssistant  useCacheRef  
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }
public Cache useCacheRef(String namespace) {
    if (namespace == null) {
      throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
      unresolvedCacheRef = true;
      Cache cache = configuration.getCache(namespace);
      //cache                    ,         
      if (cache == null) {
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
      }
      //      cache currentCache       
      currentCache = cache;
      unresolvedCacheRef = false;
      return cache;
    } catch (IllegalArgumentException e) {
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    }
  }

Sql文ブロック
1.公式文書
この要素は、他の文に含めることができる再利用可能なSQLコードセグメントを定義するために使用できます.静的に(パラメータをロード)パラメータ化することができる.異なる属性値は、含むインスタンスによって変化する.例:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

このSQLフラグメントは、次のような他の文に含めることができます.
<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2</select>

属性値は、含まれるrefid属性または含まれる文の属性値に使用できます.たとえば、次のようにします.
<sql id="sometable">
  ${prefix}Table</sql>
<sql id="someinclude">
  from
    <include refid="${include_target}"/></sql>
<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="prefix" value="Some"/>
    <property name="include_target" value="sometable"/>
  </include></select>

2.ソース解析
Sql文ブロックとマッピング文は解析時にDatabaseIdによって区別され、databaseIdとdatabaseIdを持たない同じ文が同時に見つかった場合、後者は破棄されます.これにより、文を構成するときに異なるデータベースに異なる文を構成できます.
//XMLMapperBuilder  
private void sqlElement(List<XNode> list) throws Exception {
//             databaseIdProvider
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      id = builderAssistant.applyCurrentNamespace(id, false);
      //    databaseId            
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
//    ID       
        sqlFragments.put(id, context);
      }
    }
  }
  
  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
//     databaseIdProvider,       
    if (requiredDatabaseId != null) {
      if (!requiredDatabaseId.equals(databaseId)) {
        return false;
      }
} else {
  //      databaseIdProvider,      databaseId 
      if (databaseId != null) {
        return false;
      }
      //       ID databaseId   ,   
      if (this.sqlFragments.containsKey(id)) {
        XNode context = this.sqlFragments.get(id);
        if (context.getStringAttribute("databaseId") != null) {
          return false;
        }
      }
    }
    return true;
  }

最後のsqlコードブロックがノード全体をMapに格納するのは、sqlが動的多重化を実現できるため、sqlコードブロックの値を毎回再解析する必要があり、これらは次のマッピング文の解析部分で完了する.
マッピングステートメント
マッピング文は、Mapper構成の複雑な部分であり、sql文ブロックを埋め込むことができる一方で、動的Sqlもあります.
//XMLMapperBuilder  
private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        //    XMLStatementBuilder      
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }
//XMLStatementBuilder  
public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//           ,     flushCache userCache    
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    //1.   sql   (include)
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    //2.   selectKey   
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    //3.    SQL  
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

まずの解析を見てみましょう.公式ドキュメントにはサブノードが含まれています.
ユーザーはのために異なる値を構成して動的多重化を実現することができ、を構成しなければ、MybatisはXMLプロファイルから探します.
public void applyIncludes(Node source) {
Properties variablesContext = new Properties();
//  XML      property 
    Properties configurationVariables = configuration.getVariables();
    if (configurationVariables != null) {
      variablesContext.putAll(configurationVariables);
    }
    applyIncludes(source, variablesContext);
  }
  private void applyIncludes(Node source, final Properties variablesContext) {
//  <include>    
    if (source.getNodeName().equals("include")) {
      //     fullContext  ,                      
Properties fullContext;
      String refid = getStringAttribute(source, "refid");
      // refid    (  refid="${include_target}"   )
      refid = PropertyParser.parse(refid, variablesContext);
      Node toInclude = findSqlFragment(refid);
      //   <property>         
      Properties newVariablesContext = getVariablesContext(source, variablesContext);
      //            
      if (!newVariablesContext.isEmpty()) {
        // merge contexts
        fullContext = new Properties();
        fullContext.putAll(variablesContext);
        fullContext.putAll(newVariablesContext);
      } else {
        // no new context - use inherited fully
        fullContext = variablesContext;
      }
      //  Sql     ,toInclude <sql>  
      applyIncludes(toInclude, fullContext);
      if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
        toInclude = source.getOwnerDocument().importNode(toInclude, true);
      }
      // <include>     <sql>
      source.getParentNode().replaceChild(toInclude, source);
      while (toInclude.hasChildNodes()) {
//  <sql>      sql  
        toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
      }
      //    <sql>  
      toInclude.getParentNode().removeChild(toInclude);
}else if (source.getNodeType() == Node.ELEMENT_NODE) {
      NodeList children = source.getChildNodes();
      for (int i=0; i<children.getLength(); i++) {
        applyIncludes(children.item(i), variablesContext);
      }
    } else if (source.getNodeType() == Node.ATTRIBUTE_NODE && !variablesContext.isEmpty()) {
      // replace variables in all attribute values
      source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
    } else if (source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {
      // replace variables ins all text nodes
      source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
}
  }
以上のプロセスは、最終的にを対応するsql文に置き換えます.
次にの解析を行い、configurationのkeyGeneratorsに保存される特殊なマッピング文と見なすことができます.
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
List<XNode> selectKeyNodes = context.evalNodes("selectKey");
//     databaseId
    if (configuration.getDatabaseId() != null) {
      parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
    }
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
//       
removeSelectKeyNodes(selectKeyNodes);
}
private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
    for (XNode nodeToHandle : list) {
      String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
      String databaseId = nodeToHandle.getStringAttribute("databaseId");
            if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
        parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
      }
    }
  }
private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
    String resultType = nodeToHandle.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
    String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
    boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));

    //defaults
    boolean useCache = false;
    boolean resultOrdered = false;
    KeyGenerator keyGenerator = new NoKeyGenerator();
    Integer fetchSize = null;
    Integer timeout = null;
    boolean flushCache = false;
    String parameterMap = null;
    String resultMap = null;
    ResultSetType resultSetTypeEnum = null;

    SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
    //   <selectKey>    select  
    SqlCommandType sqlCommandType = SqlCommandType.SELECT;
//       ,<selectKey>   MappedStatement     ,
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);

    id = builderAssistant.applyCurrentNamespace(id, false);
//          configuration 
    MappedStatement keyStatement = configuration.getMappedStatement(id, false);
    configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
  }

最後にマッピング文の解析
public void parseStatementNode() {
    //....  
    
//3.    SQL  
//    SqlSource  ,      Sql  
//    langDriver  ,         Sql  ,    
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
//    KeyGenerator 
KeyGenerator keyGenerator;
//keyStatementId  <SelectKey> id      ,         <SelectKey>     
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

以上のコードから,マッピング文は最終的にMapperBuilderAssistantによってMappedStatementオブジェクトに解析され,最後にこのプロセスがどのように実現されるかを見ることができる.
//MapperBuilderAssistant 
 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) {
//  cache-ref         
    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//   statementBuilder  
    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)
        .resulSets(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;
  }