This is the 17th day of my participation in the August Text Challenge.More challenges in August

Sequence diagram

sequenceDiagram participant A as XMLConfigBuilder participant B as Configuration participant C as XMLMapperBuilder participant D as MapperRegistry participant E as MapperAnnotationBuilder A ->> A: The mapperElement Alt subtag is package A ->> B: addMappers B ->> D: addMappers D -> D: addMapper D ->> E: parse E ->> E: A ->> C: parse C ->> C: configurationElement C ->> C: configurationElement C ->> C: BindMapperForNamespace C ->> B: addMapper B ->> D: addMapper else Parse URL attributes A ->> C: Parse else parse class attributes A ->> B: addMapper B ->> D: addMapper D ->> E: parse end end
  • The package configuration is similar to the class configuration execution logic, and only the package configuration code is followed here
  • Resource configuration and url configuration of the way to execute the logic, here only with the configuration of the resource code

Steps,

  • mapperElementmethods
/** * Resolves the mappers node, for example:  * <mappers> * <mapper resource="resources/xml/PurchaseMapper.xml"/> * <package name="org.apache.ibatis.z_run.mapper"/> * </mappers> *@paramParent mappers node *@throws Exception
 */
private void mapperElement(XNode parent) throws Exception {
    if(parent ! =null) {
        for (XNode child : parent.getChildren()) {
            // Process the child nodes of Mappers, namely the mapper node or the package node
            if ("package".equals(child.getName())) { / / package node
                // Retrieve the package path
                String mapperPackage = child.getStringAttribute("name");
                // Take all Mapper interfaces under the package, parse the corresponding XML, and add them all to Mappers
                configuration.addMappers(mapperPackage);
            } else {
                // Only one of the resource, URL, and class attributes takes effect
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if(resource ! =null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    // Get the input stream of the file
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    // Use XMLMapperBuilder to parse the mapping file
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null&& url ! =null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    // Get input streams from the network
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    // Use XMLMapperBuilder to parse the mapping file
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url == null&& mapperClass ! =null) {
                    // Load the configured mapping interface, get the corresponding XML, and add it to MappersClass<? > 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.");
                }
            }
        }
    }
}
Copy the code

How to configure packages

Configuration#addMappers

// Map the registry
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

public void addMappers(String packageName) {
    /** * The mapperRegistry is created in the Configuration object, and the current Configuration object is passed in *. Therefore, all subsequent operations are performed on the same Configuration object, i.e. the current object */
    mapperRegistry.addMappers(packageName);
}
Copy the code

MapperRegistry#addMappers

/ * * *@since3.2.2 * /
public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
}

/ * * *@since3.2.2 * /
/** * All files whose parent is Object *@paramPackageName package name *@param superType Object
 */
public void addMappers(String packageName, Class
        superType) {
    // Get the class file in the packageResolverUtil<Class<? >> resolverUtil =new ResolverUtil<>();
    resolverUtil.find(newResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<? >>> mapperSet = resolverUtil.getClasses();// Parse the interface and put the parse results into mappedStatements in Configuration
    for (Class<?> mapperClass : mapperSet) {
        addMapper(mapperClass);
    }
}

public <T> void addMapper(Class<T> type) {
    // Only Class objects of interface type are handled
    if (type.isInterface()) {
        // Check whether the current interface has been resolved
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            // Put the current interface into knownMappers to prevent repeated parsing
            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);
            // Start parsing
            parser.parse();
            loadCompleted = true;
        } finally {
            // After parsing fails, the current interface is removed from knownMappers
            if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code

MapperAnnotationBuilder#parse

/** * Parse the interface document with annotations */
public void parse(a) {
    String resource = type.toString();
    // Prevent duplicate analysis
    if(! configuration.isResourceLoaded(resource)) {// Find if there is XML configuration in the resource path corresponding to the class name. If there is XML configuration in the resource path, parse it directly, so that annotations and XML can be mixed
        loadXmlResource();
        // Record the resource path
        configuration.addLoadedResource(resource);
        // Set the namespace
        assistant.setCurrentNamespace(type.getName());
        // Handle the cache
        // Parse CacheNamespace annotations
        parseCache();
        // Parse the CacheNamespaceRef annotation
        parseCacheRef();
        // Get all the methods in the interface
        Method[] methods = type.getMethods();
        // traversal parsing
        for (Method method : methods) {
            try {
                // Exclude bridge methods. Bridge methods are automatically introduced by the compiler to match the type erasure of generics. They are not user-written methods, so exclude them.
                // issue #237
                if(! method.isBridge()) {// Parse the methodparseStatement(method); }}catch (IncompleteElementException e) {
                // Exception methods are temporarily stored
                configuration.addIncompleteMethod(new MethodResolver(this, method)); }}}// How to handle exceptions
    parsePendingMethods();
}
Copy the code

MapperAnnotationBuilder#parseStatement

  • Only annotations can be parsed, not XML
/** * parses the method, mainly parsing the annotation information on the method *@param method
 */
void parseStatement(Method method) {
    // Use the word method to get the parameter typeClass<? > parameterTypeClass = getParameterType(method);// Get the scripting language channel for the method
    LanguageDriver languageDriver = getLanguageDriver(method);
    // Get the SqlSource via annotations
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if(sqlSource ! =null) {
        // Get the configuration information that may exist on the method, specified by the @options annotation
        Options options = method.getAnnotation(Options.class);
        final String mappedStatementId = type.getName() + "." + method.getName();
        Integer fetchSize = null;
        Integer timeout = null;
        StatementType statementType = StatementType.PREPARED;
        ResultSetType resultSetType = configuration.getDefaultResultSetType();
        SqlCommandType sqlCommandType = getSqlCommandType(method);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        booleanflushCache = ! isSelect;boolean useCache = isSelect;

        KeyGenerator keyGenerator;
        String keyProperty = null;
        String keyColumn = null;
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
            // first check for SelectKey annotation - that overrides everything else
            SelectKey selectKey = method.getAnnotation(SelectKey.class);
            if(selectKey ! =null) {
                keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
                keyProperty = selectKey.keyProperty();
            } else if (options == null) {
                keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            } else{ keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; keyProperty = options.keyProperty(); keyColumn = options.keyColumn(); }}else {
            keyGenerator = NoKeyGenerator.INSTANCE;
        }

        if(options ! =null) {
            if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
                flushCache = true;
            } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
                flushCache = false;
            }
            useCache = options.useCache();
            fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
            timeout = options.timeout() > -1 ? options.timeout() : null;
            statementType = options.statementType();
            if(options.resultSetType() ! = ResultSetType.DEFAULT) { resultSetType = options.resultSetType(); } } String resultMapId =null;
        ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
        if(resultMapAnnotation ! =null) {
            resultMapId = String.join(",", resultMapAnnotation.value());
        } else if (isSelect) {
            resultMapId = parseResultMap(method);
        }

        // Save the obtained information to configuration
        assistant.addMappedStatement(
                mappedStatementId,
                sqlSource,
                statementType,
                sqlCommandType,
                fetchSize,
                timeout,
                // ParameterMapID
                null,
                parameterTypeClass,
                resultMapId,
                getReturnType(method),
                resultSetType,
                flushCache,
                useCache,
                // TODO gcode issue #577
                false,
                keyGenerator,
                keyProperty,
                keyColumn,
                // DatabaseID
                null,
                languageDriver,
                // ResultSetsoptions ! =null ? nullOrEmpty(options.resultSets()) : null); }}Copy the code

configurationresourceThe way of

XMLMapperBuilder#parse

/** * parse the mapping file */
public void parse(a) {
    // Whether the node has been parsed
    if(! configuration.isResourceLoaded(resource)) {// Process the mapper node
        configurationElement(parser.evalNode("/mapper"));
        // Join the parsed list to prevent repeated parsing
        configuration.addLoadedResource(resource);
        // Register Mapper with Configuration
        bindMapperForNamespace();
    }

    
      
       , 
       
        , SQL statements that fail
       
      
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}
Copy the code

XMLMapperBuilder#configurationElement

  • Parsing only XML
/** * Parse the mapping file's lower node *@paramContext mapping file root */
private void configurationElement(XNode context) {
    try {
        // Reads the namespace of the current mapping file
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        // Resolve other configuration nodes in the mapping file
        // Parse the cache label
        cacheRefElement(context.evalNode("cache-ref"));
        cacheElement(context.evalNode("cache"));
        // Parse the parameter mapping
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // Parse the result set mapping
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // Parse SQL tags
        sqlElement(context.evalNodes("/mapper/sql"));
        // Process individual database operation statements
        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); }}Copy the code

XMLMapperBuilder#buildStatementFromContext

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) {
        // Create a parse object
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            / / parsing
            statementParser.parseStatementNode();
        } catch(IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); }}}Copy the code

XMLStatementBuilder#parseStatementNode

/** * parse select, INSERT, update, delete nodes */
public void parseStatementNode(a) {
    // Read the current node ID and databaseId
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    // Verify that id matches databaseId. MyBatis allows multiple database configurations, so some statements only apply to specific databases
    if(! databaseIdMatchesCurrent(id, databaseId,this.requiredDatabaseId)) {
        return;
    }

    // Read the node name
    String nodeName = context.getNode().getNodeName();
    // Read and determine the statement type SqlCommandType
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered".false);

    // Process the Include node in the statement
    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parameter type
    String parameterType = context.getStringAttribute("parameterType");
    /** * 1. Find the corresponding class in the alias mapper according to the attribute value * 2. If not in the alias mapper, the Class object */ corresponding to the Class name is createdClass<? > parameterTypeClass = resolveClass(parameterType);// Statement type. The default value is XMLLanguageDriver
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    / / processing SelectKey node where KeyGenerator will be added to the Configuration. The keyGenerators
    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // At this point, the 
      
        and 
       
         nodes have been parsed and deleted to begin SQL parsing
       
      
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    // Determine if there is a resolved KeyGenerator
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        // Automatic key generation is used globally or whenever automatic key generation is enabled in this statement
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
                ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // Create the SqlSource based on the LanguageDriver obtained
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    // Read the configuration properties
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType"); Class<? > resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
        resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    // Create the MappedStatement object with the help of the MapperBuilderAssistant and write it to the Configuration
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
Copy the code

XMLMapperBuilder#bindMapperForNamespace

private void bindMapperForNamespace(a) {
    // Get the current namespace
    String namespace = builderAssistant.getCurrentNamespace();
    if(namespace ! =null) { Class<? > boundType =null;
        try {
            // Get the corresponding Class object according to the namespace
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
        }
        if(boundType ! =null) {
            if(! 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
                // The current XML file has been parsed
                configuration.addLoadedResource("namespace:" + namespace);
                // Set the Class object corresponding to the current XML to the Configuration, and parse whether the interface corresponding to the current XML contains annotationsconfiguration.addMapper(boundType); }}}}Copy the code

Configuration#addMapper

public <T> void addMapper(Class<T> type) {
    // Add the current interface to the mapping
    mapperRegistry.addMapper(type);
}
Copy the code

MapperRegistry#addMapper

public <T> void addMapper(Class<T> type) {
    // Only Class objects of interface type are handled
    if (type.isInterface()) {
        // Check whether the current interface has been resolved
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            /** * 1. Put the current interface into knownMappers to prevent repeated parsing * 2. After wrapping the interface with MapperProxyFactory, place it in the mapping */
            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.
            // A parser that parses annotations
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            // Start parsing
            parser.parse();
            loadCompleted = true;
        } finally {
            // After parsing fails, the current interface is removed from knownMappers
            if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code

This is how mappers tag parsing goes, and as you can see, configuration using package name and class only resolves annotation-based SQLSources, and configuration using URL and Resource only resolves XML-based SQLsources. For parameter mapping, result set mapping, and SqlSource object parsing, see next breakdown!