【緊急避難所】mybatisはXMLと注記を併用できます


【緊急避難所】mybatisはXMLと注記を併用できます
  • 結論
  • の使い方
  • 方式一
  • 方式2:
  • 方式3:
  • 注意:

  • 原因分析
  • 方式が成立した原因
  • 方式2の限界
  • 方式三成立の原因
  • 注意



  • 結論を先に述べる.
    MyBatisはXMLと注釈を併用して構成できます.
    使用方法
    プロジェクトにMapper:com.inaction.webmybatisinaction.UserMapperと彼のXMLプロファイルがresourceディレクトリの下に配置されていると仮定します:UserMapper.xml
    方式一
    XMLのresourceパス(またはURLパス)のみ明記
    <mappers>
        <mapper resource="UserMapper.xml"/>
     mappers>
    

    方式2:
    注釈Mapperのクラスフルパス名のみを明記します(この方法は注釈のみを含む構成に適しています).
    <mappers>
        <mapper class="com.inaction.webmybatisinaction.UserMapper"/>
    mappers>
    

    方式3:
    同時に明記しますが、クラスのフルパス名はxmlの前に書かなければなりません.
    <mappers>
        <mapper class="com.inaction.webmybatisinaction.UserMapper"/>
        <mapper resource="UserMapper.xml"/>
    mappers>
    

    注意:
    XMLと注釈の2つの方式を同時に採用して構成することができますが、同じ方法に対して同時に注釈とXMLの構成を行うことはできません.そうしないと、エラーが発生します.
    原因分析
    方式が成立した原因
    SqlSessionFactoryの作成中にコンフィギュレーションオブジェクトが先に作成され、SqlMapConfig.xmlのノードが先に解析され、最後に解析されるのがノードで、XML MapperBuilderのparse()メソッド解析が呼び出され、XMLのメソッドノードが解析されると、XMLファイルがコンフィギュレーションに設定されていることを解析した後、名前空間バインドの操作が行われます:bindMapperForNamespace()
    //java XMLMapperBuilder 
    public void parse() {
         
      if (!configuration.isResourceLoaded(resource)) {
         
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
      }
    
      parsePendingResultMaps();
      parsePendingCacheRefs();
      parsePendingStatements();
    }
    private void bindMapperForNamespace() {
         
      String namespace = builderAssistant.getCurrentNamespace();
      if (namespace != null) {
         
        Class<?> boundType = null;
        try {
         
          boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
         
          //ignore, bound type is not required
        }
        if (boundType != null) {
         
          if (!configuration.hasMapper(boundType)) {
         
          //            Mapper               
            // Spring may not know the real resource name so we set a flag
            // to prevent loading again this resource from the mapper interface
            // look at MapperAnnotationBuilder#loadXmlResource
            configuration.addLoadedResource("namespace:" + namespace);
            configuration.addMapper(boundType);
          }
        }
      }
    }
    

    この操作はまずコンフィギュレーションが事前にMapperオブジェクトを解析したかどうかを判断し,事前に解析した場合は処理をせずにそのまま終了し,解析しなかった場合はXMLファイルに配置されたネーミングスペースを介して対応するMapperクラスに反射し,一連の反射操作により注釈を解析する.したがって,XMLファイルパスのみを明記すると,依然としてMapper注釈に解析できる.
    方式二の限界
    この方式では、コンフィギュレーションのMapperRegistryに直接Mapperを登録しますが、MapperオブジェクトがXMLの位置を知らないため、XMLの構成は解析されません.だからこの方法は安全ではありません.
    //      MapperRegistry   ,    Mapper      XML
    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<>(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);
            }
          }
        }
      }
    

    方式三が成立した原因
    方式三一は必ずクラスの配置をxmlの配置の前に書いて、先にmapperを解析した後に、引き続きxmlを解析することができると言って、xmlを解析する時にmapperが解析したことを判断した後に、繰り返し解析しても間違いがないと判断して、しかし先にxmlを解析するならば、Configurationの中でMapperを登録することができて、その後、Mapperの解析中にロードが検出されると例外が放出され、SqlSessionFactoryの作成が終了します.
    //           ,     hasMapper(type)         Mapper,         。
     public <T> void addMapper(Class<T> type) {
         
        if (type.isInterface()) {
         
          if (hasMapper(type)) {
         
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
    

    に注意
    XMLと注釈の2つの方式を同時に採用して構成することができますが、同じ方法に対して同時に注釈とXMLの構成を行うことはできません.そうしないと、エラーが発生します.各sqlmapを解析するときに一意のIDが生成され、MapperRegistryに格納されるため、この登録センターは本質的にHashMapであり、既存のkey値を挿入することは許されず、挿入操作をするときに同名のIDが検出されるとエラーで解析が終了する.したがって、1つのメソッドの注釈とXML構成は許可されません.
    //   Configuration     ,         
    public void addMappedStatement(MappedStatement ms) {
         
        mappedStatements.put(ms.getId(), ms);
      }
    
    //   mappedStatements      StictMap     ,              
    	@Override
        @SuppressWarnings("unchecked")
        public V put(String key, V value) {
         
          if (containsKey(key)) {
         
            throw new IllegalArgumentException(name + " already contains value for " + key
                + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
          }
          if (key.contains(".")) {
         
            final String shortKey = getShortName(key);
            if (super.get(shortKey) == null) {
         
              super.put(shortKey, value);
            } else {
         
              super.put(shortKey, (V) new Ambiguity(shortKey));
            }
          }
          return super.put(key, value);
        }