[emergency shelter] mybatis can use XML and annotation at the same time

[emergency shelter] mybatis can use XML and annotation at the same time

Put the conclusion first

MyBatis can be configured using both XML and annotations.

How do you use it?

Suppose there is one in the project Mapper:com.inaction.webmybatisinaction.UserMapper And his XML configuration file are placed in the resource directory: UserMapper.xml

Mode 1

Write only the resource path (or URL path) of XML

<mappers>
    <mapper resource="UserMapper.xml"/>
 </mappers>

Mode 2:

Only annotate Mapper's class full pathname (this method is only suitable for annotation only configurations)

<mappers>
    <mapper class="com.inaction.webmybatisinaction.UserMapper"/>
</mappers>

Mode 3:

It is indicated at the same time, but the class full pathname must be written in front of xml

<mappers>
    <mapper class="com.inaction.webmybatisinaction.UserMapper"/>
    <mapper resource="UserMapper.xml"/>
</mappers>

be careful:

Although XML and annotation can be used for configuration at the same time, you cannot annotate and configure the same method at the same time, or an error will be reported.

Cause analysis

Reason for the establishment of mode 1

In the process of creating SqlSessionFactory, the Configuration object will be created first, and it will be resolved first SqlMapConfig.xml The last node to be parsed is the node, in which the parse() method of XMLMapperBuilder will be called for parsing. When the XML mode node is parsed, a namespace binding operation will be performed after parsing the XML file Configuration into Configuration: bindMapperForNamespace();

//XMLMapperBuilder class of java
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)) {
      //If Mapper has been registered before, there will be no duplicate registration or error
        // 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);
      }
    }
  }
}

This operation first determines whether the Configuration has parsed the Mapper object in advance. If it has parsed the Mapper object in advance, it will exit without processing. If it has not parsed it, it will reflect to the corresponding Mapper class through the namespace configured in the XML file, and then parse the annotation through a series of anti reflection operations. Therefore, only the path of XML file can be parsed to Mapper annotation.

Limitations of mode 2

In this way, Mapper will be registered directly with MapperRegistry of Configuration, but the Mapper object will not parse the Configuration in XML because it does not know the location of XML. So this way is not safe.

//This method is located in the MapperRegistry class. It can only parse Mapper annotations, but not 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);
        }
      }
    }
  }

Reasons for the establishment of mode 3

Method 3: the Configuration of the class must be written before the Configuration of xml. It is said that after the mapper is parsed, the xml can be parsed continuously. If it is judged that the mapper is parsed, it will not be parsed repeatedly or thrown wrongly. However, if the xml is parsed first, the mapper will be registered in the Configuration. If the mapper is parsed later, an exception will be thrown and the program creation will be terminated Build SqlSessionFactory.

//This code refers to the second point of analysis. Here, hasMapper(type) will be called to check whether Mapper has been registered. If it has been parsed, it will be thrown in error.
 public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }

be careful

Although XML and annotation can be used for configuration at the same time, you cannot annotate and configure the same method at the same time, or an error will be reported. Because when parsing each sqlmap, a unique ID will be generated and stored in MapperRegistry. This registry is essentially a HashMap, and it is not allowed to insert the existing key value. When inserting, if an ID with the same name is detected, an error will be reported to terminate the analysis. Therefore, it is not allowed to annotate and configure a method with XML.

//This is a method in the Configuration class, which is used to register statements
public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }
//This is the implementation method of StictMap, the internal implementation class of mappedStatements. The first step is to check the repetition and report the error
	@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);
    }

Tags: xml Mybatis Java Spring

Posted on Sun, 21 Jun 2020 02:13:16 -0400 by KashKurcura