Parsing mechanism of MyBatis - mapper.xml

The last node is mapper, that is, the paths of mapper.xml introduced in the MyBatis global configuration file. And the analysis here uses one   XMLMapperBuilder   Completed by API:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // Package scan Mapper interface
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else {
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                // Process mapper.xml loaded by resource
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url != null && mapperClass == null) {
                    // Process mapper.xml loaded by url
                    ErrorContext.instance().resource(url);
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url == null && mapperClass != null) {
                    // Register a single Mapper interface
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}

So the focus of our next research is to go deep   XMLMapperBuilder   See how it parses mapper.xml.

We still use the Debug carrier copied from the beginning   MyBatisApplication6  , Enter after Debug runs   mapperElement   Method, the breakpoint stops.

1. XMLMapperBuilder

Take a general look first   XMLMapperBuilder   In itself, its internal structure is still worth studying.

1.1 inheritance and internal members

Open the source code and find it impressively from the inheritance relationship   XMLMapperBuilder  , Also inherited from   BaseBuilder   Of:

public class XMLMapperBuilder extends BaseBuilder {

  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;

Do you realize the previous chapter 7, BaseBuilder   It's a basic constructor.

Then focus on the members:

  • XPathParser parser  : I'm familiar with it. I know from Chapter 7 that it is a parser for parsing XML files. It is also used to parse mapper.xml here
  • MapperBuilderAssistant builderAssistant  : Construct Mapper's Builder assistant (as for why it is an assistant, simply put, it uses some builders internally to help us construct   ResultMap  , MappedStatement   Wait, we don't need to control ourselves, so it's called "assistant")
  • Map<String, XNode> sqlFragments  : Encapsulates reusable SQL fragments (as mentioned in the previous chapter)  < sql>   (fragment)
  • String resource  : File path of mapper.xml

At first glance, there's nothing to emphasize. Take out what you encounter below.

1.2 definition of construction method

The above source code will be called first   XMLMapperBuilder   The construction method of several parameters, and after the construction method goes down, it is a set of assignment operations, which is not interesting.

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
            configuration, resource, sqlFragments);
}

private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
}

Just pay attention to one small detail: mapperbuilder assistant   Create here. this   MapperBuilderAssistant   We'll see what we've done right away.

1.3 core parse method

After the construction is completed, you can call   parse   Method (at this time, mapper.xml has been read by IO and encapsulated as   InputStream  ), This method contains a lot of information. Let's analyze it line by line. First leave a general comment in the source code:

public void parse() {
    // If the current xml resource has not been loaded
    if (!configuration.isResourceLoaded(resource)) {
        // 2. Parse mapper element
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        // 3. Resolve and bind namespace
        bindMapperForNamespace();
    }

    // 4. Parse resultMap
    parsePendingResultMaps();
    // 5. Parse cache ref
    parsePendingCacheRefs();
    // 6. Parse the statement of the declaration
    parsePendingStatements();
}

Next, we will analyze the key codes marked with serial numbers in the source code line by line. However, we will not explore the specific and in-depth. The following volume has chapters dedicated to analyzing the life cycle and execution process. At that time, we will carefully study the internal details of MyBatis.

2. configurationElement

configurationElement(parser.evalNode("/mapper"));   This code only knows from the last parameter that it is the top level for parsing mapper.xml  < mapper>   Tags. All tags will be scanned during the analysis of this part. For details, we can take a look at the source code and comments:

private void configurationElement(XNode context) {
    try {
        // Extract the namespace corresponding to mapper.xml
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.isEmpty()) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        // Parsing cache and cache ref
        cacheRefElement(context.evalNode("cache-ref"));
        cacheElement(context.evalNode("cache"));
        // Parse and extract parametermap (the official document says it has been discarded and is no longer available)
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // Parse and extract resultMap
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // Parsing encapsulated SQL fragments
        sqlElement(context.evalNodes("/mapper/sql"));
        // Construct Statement
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
}

Then we explain the parsing of these tags line by line and what the bottom layer does.

2.1 extract namespace

    // Extract the namespace corresponding to mapper.xml
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);

When we first learned MyBatis, we knew that every mapper.xml needs a declaration   namespace  , Even if we write about abcdefg, we can't do without it. The non empty check is reflected in the source code. The design of non empty namespace takes into account the L2 cache (a   namespace   It corresponds to a L2 cache). On the other hand, it is also considered that there may be statements with the same name in different mapper.xml (for example, both department and user)   findAll  , Passed at this time   namespace   The two statements can be well separated.

2.2 parsing cache and cache ref

    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));

of  < cache>  ,< cache-ref>   The label was mentioned in Chapter 8 before. Let's just briefly mention it here. There is a special chapter behind the L2 cache.

The core action of these two steps is to parse to see if there are references in mapper.xml   namespace   And take a look at this   namespace   Whether the L2 cache is enabled, if so, configure it yourself. As for the underlying configuration, we will discuss it later in the L2 cache.

2.3 parsing and extracting resultMap [complex]

    // Parse and extract resultMap
    resultMapElements(context.evalNodes("/mapper/resultMap"));

As we mentioned in Chapter 8, the mapping configuration of resultMap result set is one of the most powerful features of MyBatis. Naturally, its processing logic will be quite complex. The little friends have a mental preparation first. Take a deep breath. We'll kill them and see what's going on inside.

private void resultMapElements(List<XNode> list) {
    for (XNode resultMapNode : list) {
        try {
            resultMapElement(resultMapNode);
        } catch (IncompleteElementException e) {
            // ignore, it will be retried
        }
    }
}

There may be more than one mapper.xml file  < resultMap>   Tag, there must be a for loop here.

Note that the try catch structure here is placed in the body of the for loop. This is to prevent a resultMap from being parsed together with other resultmaps in mapper.xml when it fails to parse. After this design, even if a resultMap fails to parse, you can continue to parse the remaining resultmaps.

When you enter the analysis method of a single resultMap, there seems to be a lot of logic, but in fact the organization is very clear. We can read through the source code first and understand it with the notes I marked:

private ResultMap resultMapElement(XNode resultMapNode) {
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
}

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // Resolve the target result set entity type of resultMap mapping
    String type = resultMapNode.getStringAttribute("type",
                         resultMapNode.getStringAttribute("ofType", 
                         resultMapNode.getStringAttribute("resultType", 
                         resultMapNode.getStringAttribute("javaType"))));
    // Load target result set entity type
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
        typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    // Resolve the sub tag of resultMap and encapsulate it as resultMapping
    for (XNode resultChild : resultChildren) {
        if ("constructor".equals(resultChild.getName())) {
            processConstructorElement(resultChild, typeClass, resultMappings);
        } else if ("discriminator".equals(resultChild.getName())) {
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
        } else {
            List<ResultFlag> flags = new ArrayList<>();
            if ("id".equals(resultChild.getName())) {
                flags.add(ResultFlag.ID);
            }
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
    }
    
    // Get the id of resultMap, inherited resultMap id and autoMapping
    String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    // Processing resultMap with ResultMapResolver
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, 
            typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
        return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
        // Parsing failed, indicating that the information of the resultMap tag is incomplete. It is recorded in the global Configuration and an exception is thrown
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
    }
}

After walking through the source code, do you feel not very confused? The idea is still quite clear? In fact, this also reflects the clear logical idea in the source code of MyBatis. Let's explain the more complex part of this source code in sections.

2.3.1 parsing result set target type

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    String type = resultMapNode.getStringAttribute("type",
                         resultMapNode.getStringAttribute("ofType", 
                         resultMapNode.getStringAttribute("resultType", 
                         resultMapNode.getStringAttribute("javaType"))));
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
        typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    // ......

The purpose of this paragraph is to parse the entity class type of the target result set. All four attributes mentioned above can be written, and one must be written. If none is written, DTD exceptions will be reported when parsing xml (attributes are required)   "type"  , And must be an element type   "resultMap"   Specify the attribute). The priority is   type > ofType > resultType > javaType  .

2.3.2 parsing result set mapping configuration

    // ......
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    // Resolve the sub tag of resultMap and encapsulate it as resultMapping
    for (XNode resultChild : resultChildren) {
        if ("constructor".equals(resultChild.getName())) {
            processConstructorElement(resultChild, typeClass, resultMappings);
        } else if ("discriminator".equals(resultChild.getName())) {
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
        } else {
            List<ResultFlag> flags = new ArrayList<>();
            if ("id".equals(resultChild.getName())) {
                flags.add(ResultFlag.ID);
            }
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
    }
    // ......

This paragraph is very complicated. The main work here is analysis  < resultMap>   As there are only three kinds of tags that can be written (ordinary mapping, constructor mapping and discriminator mapping), the if else structure here also looks relatively simple. Of course, it is only limited to appearance. The internal parsing of these sub tags is complicated. Let's start with the simplest else.

2.3.2.1 parsing common mapping labels

Common mapping tags include id, result, association and collection, which are all used when we learn the basics of MyBatis. The core method of parsing is   buildResultMappingFromContext  , Let's go in and have a look: (hot eye warning)

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
        property = context.getStringAttribute("name");
    } else {
        property = context.getStringAttribute("property");
    }
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    String nestedResultMap = context.getStringAttribute("resultMap", () ->
            processNestedResultMappings(context, Collections.emptyList(), resultType));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", 
                             configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    // Resolution of result set type and typeHandler type
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    return builderAssistant.buildResultMapping(resultType, property, column, 
                   javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, 
                   notNullColumn, columnPrefix, typeHandlerClass, 
                   flags, resultSet, foreignColumn, lazy);
}

Good guy, this paragraph is all about attributes. It's boring (just a little spicy)...

What we need to know about this method is to put one line  < result property="" column="" />   Label parsing is encapsulated into a   ResultMapping   Just. For specific details, we will go to the later life cycle chapter for further analysis.

2.3.2.2 handling constructor s

We have contacted in Chapter 8 above  < constructor>   The use of the label also knows that its interior is actually encapsulated, similar to  < id>  ,< result>   So its processing logic is basically the same:

private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
    List<XNode> argChildren = resultChild.getChildren();
    for (XNode argChild : argChildren) {
        List<ResultFlag> flags = new ArrayList<>();
        flags.add(ResultFlag.CONSTRUCTOR);
        if ("idArg".equals(argChild.getName())) {
            flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
}

Come on, isn't it the same as above? Eventually called   buildResultMappingFromContext   Method to encapsulate the row by row result set mapping as   ResultMapping   It's done.

However, one additional detail should be paid attention to here. Each row above will correspond to one in the result set mapping   List<ResultFlag> flags   And it's parsing  < constructor>   When labeling, it will give it first   flags   Add a to the collection   ResultFlag.CONSTRUCTOR   Element, which will be in   buildResultMappingFromContext   Method:

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
        // The < constructor > tag uses name
        property = context.getStringAttribute("name");
    } else {
        // Common label property
        property = context.getStringAttribute("property");
    }

As you can see, < constructor >   To get the attribute name in the tag   name   instead of   property  , This small detail can be found in mapper.xml:

Maybe we don't realize it. First, in general, the entity classes mapped by the result set only have the default parameterless constructor, which is not used  < constructor>   Attributes; Second, there is no special search when writing. When you see name, you may take it for granted that it is (everyone with constant code code has a so-called "feeling". You can write it by hand without thinking too much. If you feel the same, remember to buckle * * [me too] * *) in the comment area.

2.3.2.3 processing discriminator

The parsing of the discriminator is the most special one. Its final construction types are different. Let's scan the source code first:

private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) {
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String typeHandler = context.getStringAttribute("typeHandler");
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // Parse the < case > sub tag of < discriminator > and encapsulate it into the Map
    Map<String, String> discriminatorMap = new HashMap<>();
    for (XNode caseChild : context.getChildren()) {
        String value = caseChild.getStringAttribute("value");
        String resultMap = caseChild.getStringAttribute("resultMap", 
                processNestedResultMappings(caseChild, resultMappings, resultType));
        discriminatorMap.put(value, resultMap);
    }
    // Note that Discriminator is constructed instead of ResultMapping
    return builderAssistant.buildDiscriminator(resultType, column, 
             javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}

Scanning the source code roughly, we can know at least two things: 1) the final package is a   Discriminator   Object, also by   MapperBuilderAssistant   Construction; 2)<discriminator>   Child tags for  < case>   Eventually encapsulated in a   Map   Yes. As for encapsulated   Map   We will explain what is in the life cycle later.

2.3.3 package and build ResultMap

<resultMap>   The last part of tag parsing, which uses a   ResultMapResolver   To process and finally construct   ResultMap   Object.

    // ......
    // Get the id of resultMap, inherited resultMap id and autoMapping
    String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    // Processing resultMap with ResultMapResolver
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, 
            typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
        return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
        // Parsing failed, indicating that the information of the resultMap tag is incomplete. It is recorded in the global Configuration and an exception is thrown
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
    }
}

this   ResultMapResolver  , It's a little funny to say. It's   resolve   The way is to adjust   MapperBuilderAssistant   How to:

public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}

That's the problem. Why did it do this? Are you busy? Ah, there must be a reason why people design like this. This is a foreshadowing. There will be a echo in Section 4 below.

as for   MapperBuilderAssistant   What has been done at the bottom? It is inevitable that you will be dizzy when you go deep into it. Therefore, we are the same as the above. We will explain it in the later life cycle chapter. You only need to know that what you work in the end is   MapperBuilderAssistant   That's it.

2.4 extracting SQL fragments

The next step is to extract the SQL fragments in each mapper.xml. We all know the big rules: if there is an explicit declaration of databaseId, only the SQL fragments that meet the current global databaseId will be extracted; If no databaseId is declared, all will be extracted.

Therefore, in the following source code, we will find that it parses the SQL fragment twice, and in each circular parsing, it will judge whether the SQL fragment matches the current databaseId, and if so, it will be placed in one   sqlFragments   of   Map   Medium: (key codes are annotated)

private void sqlElement(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
        // Go through it all first and extract the statement matching the SQL fragment
        sqlElement(list, configuration.getDatabaseId());
    }
    // Then extract the general SQL fragment
    sqlElement(list, null);
}

private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        String databaseId = context.getStringAttribute("databaseId");
        String id = context.getStringAttribute("id");
        id = builderAssistant.applyCurrentNamespace(id, false);
        // Identify whether the current SQL fragment matches
        if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
            sqlFragments.put(id, context);
        }
    }
}

It can be found that the processing logic is very simple, isn't it! The logic of matching SQL fragments here is quite interesting. We can study it:

(the booklet only posts the logic, and the partners can infer the specific situations by themselves, which is completely in line with the design idea of MyBatis)

private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    // If the databaseId is explicitly configured, it should be matched directly
    if (requiredDatabaseId != null) {
        return requiredDatabaseId.equals(databaseId);
    }
    // databaseId is not required, but if this SQL fragment has a declaration, it will not be received
    if (databaseId != null) {
        return false;
    }
    // If you haven't saved this SQL fragment, you can accept it directly
    if (!this.sqlFragments.containsKey(id)) {
        return true;
    }
    // skip this fragment if there is a previous one with a not null databaseId
    // Already saved? Take it out to see if there is a databaseId. If there is, it means that there is one with the same id but no databaseId configured. It doesn't matter
    // (if the same id exists, the priority with databaseId is higher than that without databaseId)
    XNode context = this.sqlFragments.get(id);
    return context.getStringAttribute("databaseId") == null;
}

2.5 parsing statement [complex]

The last part is very complex again. It will parse the declared in mapper.xml  < select>  ,< insert>  ,< update>  ,< delete>   Labels, and finally encapsulated one by one   MappedStatement  . The processing logic of databaseId is the same as that of SQL fragments. The booklet is not repeated. We should focus on how to process and parse the tags of these statement s:

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 {
            // [complex and difficult] parse statement tags one by one with the help of XML Statement Builder
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            // statement parsing fails. It will only be recorded in Configuration, but no exception will be thrown
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

After reading through the source code, it seems that there is only one thing in mind: it gives the work to others... Yes, it's up to you to parse the statement   XMLStatementBuilder   Now, notice that it's another one   Builder  ! Now we are parsing the   XmlMapperBuilder   Also a   Builder  ! So it is conceivable that there are so many source codes! Well, we'd better expand it later in the chapter of life cycle. I'm afraid the little partner will go crazy... (dog head protects life)

OK, after walking down, basically all the tag elements will be swept. Let's go back to   XmlMapperBuilder   of   parse   Method, continue down.

3. bindMapperForNamespace

What to do next   bindMapperForNamespace   Method, in essence, is to   Mapper interface is designed for dynamic proxy. We all know that using the characteristics of mapper dynamic proxy can enable us to directly take mapper interface without manipulation   SqlSession   API, write that pile of complex statementids, and it is relatively easier to maintain code.

this   bindMapperForNamespace   The method is to support this feature. Let's take a look at its underlying implementation:

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
        Class<?> boundType = null;
        try {
            // Try to load the class corresponding to the namespace
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
            // ignore, bound type is not required
        }
        // If the Mapper interface has been loaded into the class and has not been saved before, save it
        if (boundType != null && !configuration.hasMapper(boundType)) {
            // 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
            // Spring may not know the real resource name, so it sets a flag to prevent this resource from being loaded from the Mapper interface again
            configuration.addLoadedResource("namespace:" + namespace);
            configuration.addMapper(boundType);
        }
    }
}

Oh, this operation is very simple! Directly fetch the namespace and load the corresponding class with class loading. If it is loaded, save it and finish it; If it's not loaded, then nothing happens. Yes, the logic itself is quite simple, but there is a detail, that is, a few lines of single line comments in the source code:

Spring may not know the real resource name so we set a flag to prevent loading again this resource from the mapper interface.

Spring may not know the real resource name, so it sets a flag to prevent this resource from being loaded from the Mapper interface again.

What is the operation diagram? Let's explain why.

When learning the basics of MyBatis and talking about Mapper interface dynamic proxy, you should remember that there is an agreement: when configuring Mapper in MyBatis global configuration file, if package scanning is used, when scanning Mapper interface, Mapper interface and corresponding mapper.xml need to be placed in the same directory, And the name of Mapper interface should be consistent with that of mapper.xml. The underlying principle of this Convention is   When the Mapper interface package is scanned, it will automatically find the mapper.xml file with the same name in the same directory and load the parsing (the core code can be referenced)   org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#loadXmlResource  ). However, unexpected situations may occur at this time: if we configure both the mapper.xml resource path and the Mapper interface package scanning, do we not need to load both sides? This is obviously not very reasonable! So MyBatis helps us consider this layer and adds an additional identification here: whenever an existing Mapper interface is parsed, the Monogram will be displayed Remember that the mapper.xml file corresponding to this interface has been loaded, so even if the Mapper interface is read during package scanning, it will not be loaded again if it checks that it has been loaded when it wants to load mapper.xml.

The large text description may not be easy to understand. We'd better use the most acceptable expression package to help our partners understand!

For example, now we have three mapper.xml files. When MyBatis is initialized, it is global   XMLMapperBuilder   They will be gathered and loaded, and a special person will be sent to write down the loaded mapper.xml when loading.

After loading mapper.xml, MyBatis will call Mapper interface for another person to tell them that they are also ready to load.

When the mapper interface needs to be loaded, it feels that it is very important, so it takes its own path to find the mapper.xml file with the same name. Coincidentally, it may actually find the mapper.xml with the same name. It's so happy. It wants to load. At this time, the named person above suddenly appears in front of him and tells him: people have loaded it. Go OK! Mapper interface is very depressed, but people did load it. It can't say anything, so they had to go home bitterly.

With this control, MyBatis can load the mapper.xml file only once, and sort out all the Mapper interfaces that can be bound.

4. Reprocess incomplete elements

parse   The last three steps of the method actually do the same kind of things, that is, reprocess the incomplete elements saved in the previous parsing process:

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();

Let's take resultMap as an example to see its implementation:

private void parsePendingResultMaps() {
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {
        Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
        while (iter.hasNext()) {
            try {
                // Extract one by one and re parse
                iter.next().resolve();
                iter.remove();
            } catch (IncompleteElementException e) {
                // ResultMap is still missing a resource...
            }
        }
    }
}

public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}

Note that what is extracted here is a group   ResultMapResolver  , It just echoes the seemingly redundant encapsulation operation mentioned in Section 2.3.3 above! You can see this   ResultMapResolver   It's not superfluous to do such a step through   ResultMapResolver   Such an intermediate layer can save all the definition information involved in a resultMap, so that when reprocessing, you can directly find the information of the parsed resultMap and directly let the   MapperBuilderAssistant   do it again.

After the iteration, those that can be parsed normally will start from   incompleteResultMaps   Of course, MyBatis has not completely abandoned the resultmaps and statement s that cannot be parsed at this time. In the integration of MyBatis and spring framework, after the IOC container refresh is completed, these incomplete resultmaps will be parsed for the last time. We will put this part into MyBatis integration springfr In the chapter of amework.

OK, come here, xmlmaperbuilder   The whole processing logic of the is executed   mapperElement   The method processing is completed, which means   SqlSessionFactory   It has been successfully created.

Tags: Mybatis

Posted on Sat, 20 Nov 2021 18:25:30 -0500 by Yari