Mybatis(七): builder模块详解

655 阅读20分钟

本文是Mybatis相关文章的第七篇,在本篇文章中,我们会介绍下builder模块,这个模块位于org.apache.ibatis.builder包下,这个模块是负责在Mybatis启动时加载配置文件和映射文件的解析。

builder模块对XML文件的解析,使用的前面介绍的解析器模块,在这里我们不会再对这个模块的内容进行介绍,不熟悉的可以先去看第三篇文章。

1 BaseBuilder

BaseBuilder是一个抽象类,这个类是解析配置文件相关builder的顶层父类,这个类的属性如下:

// Configuration是Mybatis中的核心对象  存储了Mybatis中的所有配置
protected final Configuration configuration;
// 类型别名注册器
protected final TypeAliasRegistry typeAliasRegistry;
// 类型转换器注册器
protected final TypeHandlerRegistry typeHandlerRegistry;

BaseBuilder主要的子类如下,这些类我们会在后面的源码跟踪时陆续的进行讲解,在这里只需有个大致的了解即可。

  • XMLConfigBuilder
  • XMLMapperBuilder
  • XMLStatementBuilder
  • XMLScriptBuilder
  • MapperBuilderAssistant
  • SqlSourceBuilder
  • ParameterMappingTokenHandler

2 Mybatis配置文件的解析

配置文件的解析是Mybatis启动时需要完成的工作,大家是否还记得我们示例代码中创建SqlSessionFactory对象的入口是什么吗?是SqlSessionFactoryBuilder.build方法,在这个方法中会进行配置文件的解析,这个方法的逻辑如下:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // 创建XMLConfigBuilder对象
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // 先调用XMLConfigBuilder.parse方法进行配置类的解析获取到Configuration对象
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}

这里会创建XMLConfigBuilder对象,这个类的属性如下:

// 是否已经解析过mybatis配置文件
private boolean parsed;
// XPathParser对象,Mybatis中的解析器,为XML文件的解析提供了支持
private final XPathParser parser;
// 环境代号
private String environment;

XMLConfigBuilder.parse方法如下:

// 解析Mybatis配置文件的入口
public Configuration parse() {
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 解析<configuration>节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}
​
private void parseConfiguration(XNode root) {
    try {
        // issue #117 read properties first
        // 解析<properties>节点
        propertiesElement(root.evalNode("properties"));
        // 解析<settings>节点
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        // 设置vfsImpl字段
        loadCustomVfs(settings);
        // 设置logImpl字段
        loadCustomLogImpl(settings);
        // 解析<typeAliases>节点
        typeAliasesElement(root.evalNode("typeAliases"));
        // 解析<plugins>节点
        pluginElement(root.evalNode("plugins"));
        // 解析<objectFactory>节点
        objectFactoryElement(root.evalNode("objectFactory"));
        // 解析<objectWrapperFactory>节点
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        // 解析<reflectorFactory>节点
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        // 解析<environments>节点
        environmentsElement(root.evalNode("environments"));
        // 解析<databaseIdProvider>节点
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // 解析<typeHandlers>节点
        typeHandlerElement(root.evalNode("typeHandlers"));
        // 解析<mappers>节点
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

在这个方法中会调用解析各个节点的方法,接下来我们分别看看这些节点的解析逻辑。

2.1 properties节点解析

Mybatis会将properties节点的数据解析成一个个的键值对存放到Properties对象中,propertiesElement方法的逻辑如下:

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        // XNode中的方法,会将子节点的name和value字段存储到Properties对象中
        Properties defaults = context.getChildrenAsProperties();
        // 获取<properties>节点的resource和url属性
        String resource = context.getStringAttribute("resource");
        String url = context.getStringAttribute("url");
        // 两个不可同时存在,否则抛出异常
        if (resource != null && url != null) {
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
        }
        // 加载properties文件中的配置
        if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        // 获取variables属性的值
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        // 更新XPathParser和Configuration中的variables属性
        parser.setVariables(defaults);
        configuration.setVariables(defaults);
    }
}

2.2 setting节点解析

setting节点解析会将setting节点的子节点的namevalue属性组成键值对,存放到一个Properties对象中,settingsAsProperties方法如下:

private Properties settingsAsProperties(XNode context) {
    if (context == null) {
        return new Properties();
    }
    // XNode中的方法  获取子节点的name和value属性  组成键值对
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    // 获取Configuration类的MetaClass对象
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    // 遍历解析出的键值对
    for (Object key : props.keySet()) {
        // 判断Configuration类中是否存在该属性的setter方法  不存在抛出异常
        if (!metaConfig.hasSetter(String.valueOf(key))) {
            throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
        }
    }
    return props;
}

这个方法的逻辑也是比较简单的,这里调用了XNode中的方法,之前的文章中介绍过,这里不做过多介绍,处理逻辑如下:

  • 调用XNode.getChildrenAsProperties方法,将settings子节点中的namevalue属性组成键值对存储到properties
  • 校验name属性是否在Configuration对象中存在setter方法,不存在抛出异常

当解析出settings中的配置后,会调用loadCustomVfsloadCustomLogImpl方法设置Configuration类中的vfsImpllogImpl属性,这里不粘贴代码了。

之后会在settingsElement方法中使用解析出的值,这个方法是给Configuration中的一些属性设置值,如果没有配置则设置默认值,大家自行看源码吧。

2.3 typeAliases节点解析

typeAliass节点的解析,会将我们配置的类型别名进行注册,其逻辑如下:

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        // 获取<typeAliases>的子节点
        for (XNode child : parent.getChildren()) {
            // 如果是<package>节点
            if ("package".equals(child.getName())) {
                // 获取name属性
                String typeAliasPackage = child.getStringAttribute("name");
                // 按照包名注册配置好的类型别名
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
                // 获取别名
                String alias = child.getStringAttribute("alias");
                // 获取类型
                String type = child.getStringAttribute("type");
                try {
                    // 通过反射加载类
                    Class<?> clazz = Resources.classForName(type);
                    if (alias == null) {
                        // 如果别名为空,使用mybatis的默认名 类名
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
                        // 调用通过别名和类型的注册方法
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) {
                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                }
            }
        }
    }
}

这里使用到了TypeAliasRegistry类,我们在前面的文章中已经进行过详细的介绍了,这里就不进行说明了。

2.4 plugins节点的解析

plugins用来解析我们配置的插件,会将解析出的插件对象存储到Configuration.interceptorChain属性中,源码如下:

private void pluginElement(XNode parent) throws Exception {
    // 如果节点不存在则不处理
    if (parent != null) {
        // 遍历子节点
        for (XNode child : parent.getChildren()) {
            // 获取interceptor属性
            String interceptor = child.getStringAttribute("interceptor");
            // 获取子节点配置的属性
            Properties properties = child.getChildrenAsProperties();
            // 通过反射创建类  会先从TypeAliasRegistry中查找   不存在使用反射加载
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
            // 设置属性
            interceptorInstance.setProperties(properties);
            // 将解析出的Interceptor对象添加到InterceptorChain对象中
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

2.5 objectFactoryobjectWrapperFactoryreflectorFactory节点解析

这三个节点的解析逻辑很相似,我们一起贴出如下:

private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
        // 获取type属性
        String type = context.getStringAttribute("type");
        // 获取配置的属性
        Properties properties = context.getChildrenAsProperties();
        // 创建对象
        ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        // 设置properties
        factory.setProperties(properties);
        // 存储到Configuration.objectFactory属性中
        configuration.setObjectFactory(factory);
    }
}
​
private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        configuration.setObjectWrapperFactory(factory);
    }
}
​
private void reflectorFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        configuration.setReflectorFactory(factory);
    }
}

2.6 environments节点解析

Mybatis中支持我们根据不同的环境进行配置不同的数据库,这些配置会放到environments节点下,我们调用SqlSessionFacotryBuild.build方法时可以传递环境标识进来,如果不传会使用我们配置的default属性的值,会将解析出的数据存储到Configuration.environment属性中,其逻辑如下:

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        // environment如果为空   取default属性配置的值
        if (environment == null) {
            environment = context.getStringAttribute("default");
        }
        // 遍历子节点
        for (XNode child : context.getChildren()) {
            // 获取id属性
            String id = child.getStringAttribute("id");
            // 判断环境代号是否为environment的值
            if (isSpecifiedEnvironment(id)) {
                // 获取TransactionFactory对象
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                // 获取DataSourceFactory对象
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                // 获取DataSource对象
                DataSource dataSource = dsFactory.getDataSource();
                // 创建Environment.Builder对象
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                    .transactionFactory(txFactory)
                    .dataSource(dataSource);
                // 设置configuration.environment属性
                configuration.setEnvironment(environmentBuilder.build());
                break;
            }
        }
    }
}

2.7 typeHandlers节点解析

typeHandlers中定义了我们自定义的类型转换器,解析这个节点会将我们配置的类型转换器添加到TypeHandlerRegistry中,这个类在前面的章节中已经详细的介绍过,这里我们直接看解析的逻辑,源码如下:

private void typeHandlerElement(XNode parent) {
    if (parent != null) {
        // 遍历<typeHandlers>节点的子节点
        for (XNode child : parent.getChildren()) {
            // 如果是<package>节点
            if ("package".equals(child.getName())) {
                // 获取配置的包名
                String typeHandlerPackage = child.getStringAttribute("name");
                // 通过包名注册类型转换器
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
                // 获取配置的javaType、jdbcType和handler属性
                String javaTypeName = child.getStringAttribute("javaType");
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                String handlerTypeName = child.getStringAttribute("handler");
                // 获取javaType对应的Class对象
                Class<?> javaTypeClass = resolveClass(javaTypeName);
                // 获取JdbcType
                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                // 获取handler对应的Class文件
                Class<?> typeHandlerClass = resolveClass(handlerTypeName);
                if (javaTypeClass != null) {
                    // 注册类型转换器
                    if (jdbcType == null) {
                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                    }
                } else {
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }
}

2.8 mappers节点解析

mappers节点中定义了我们的Mapper位置,Mybatis通过解析这个标签,将Mapper注册到MapperRegistry对象中,这个对象我们在前面的章节中也介绍过了,这里我们直接看代码逻辑,如下:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 如果是package节点
            if ("package".equals(child.getName())) {
                // 获取节点的name属性
                String mapperPackage = child.getStringAttribute("name");
                // 通过报名注册mapper
                configuration.addMappers(mapperPackage);
            } else {
                // 获取resource属性
                String resource = child.getStringAttribute("resource");
                // 获取url属性
                String url = child.getStringAttribute("url");
                // 获取class属性
                String mapperClass = child.getStringAttribute("class");
                // 如果recourse属性不为空 其他两个属性为空
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    // 加载Mapper映射文件
                    try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
                        // 创建XMLMapperBuilder对象
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                        // 解析映射文件
                        mapperParser.parse();
                    }
                    // 如果url属性不为空,其他两个属性为空
                } else if (resource == null && url != null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    // 加载映射文件
                    try(InputStream inputStream = Resources.getUrlAsStream(url)){
                        // 创建XMLMapperBuilder对象
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                        // 解析映射文件
                        mapperParser.parse();
                    }
                    // 如果class属性不为空 其他两个属性为空
                } else if (resource == null && url == null && mapperClass != null) {
                    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.");
                }
            }
        }
    }
}

通过上面的代码我们发现,我们配置了映射文件地址属性的节点,会创建XMLMapperBuilder对象去进行解析,而package节点或者配置mapperClass属性的节点似乎没有配置文件进行加载。

我们知道Mybatis是支持配置文件和注解两种方式来自定义SQL的,其实上面的两种处理逻辑对应的就是这两种方式,无需解析映射文件的是配置是会处理Mapper类中注解。这两种的处理逻辑我们会在下面的内容中进行详细介绍。

Mybatis配置文件的解析我们就介绍到这里,接下来我们看看映射文件的解析。

3 Mapper映射文件的解析

通过第二部分内容的学习,负责解析映射文件的类为XMLMapperBuilder,这个类的属性如下:

// XPathParser对象  解析器
private final XPathParser parser;
// 映射文件解析辅助类
private final MapperBuilderAssistant builderAssistant;
private final Map<String, XNode> sqlFragments;
// mapper文件地址
private final String resource;

MapperBuilderAssistant类是映射文件解析的一个辅助类,这也是BaseBuilder的一个实现类,其属性如下:

// 命名空间
private String currentNamespace;
// 映射文件地址
private final String resource;
// 二级缓存
private Cache currentCache;
// 是否未解析chche-ref标记
private boolean unresolvedCacheRef;

XMLMapperBuilder.parse()方法是解析映射文件的入口,这个方法的逻辑如下:

public void parse() {
    // 判断是否已经加载过该映射文件
    if (!configuration.isResourceLoaded(resource)) {
        // 处理mapper节点
        configurationElement(parser.evalNode("/mapper"));
        // 将映射文件记录到Configuration.loadedResources字段中   是一个Set
        configuration.addLoadedResource(resource);
        // 注册Mapper
        bindMapperForNamespace();
    }
    // 处理解析失败的ResultMap
    parsePendingResultMaps();
    // 处理解析失败的cache-ref
    parsePendingCacheRefs();
    // 处理解析失败的SQL
    parsePendingStatements();
}

3.1 解析标签

confirurationElement方法是解析标签的方法,这个方法如下:

private void configurationElement(XNode context) {
    try {
        // 获取namespace属性
        String namespace = context.getStringAttribute("namespace");
        // namespace不存在抛出异常
        if (namespace == null || namespace.isEmpty()) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        // 设置builderAssistant字段的currentNamespace属性
        builderAssistant.setCurrentNamespace(namespace);
        // 解析cache-ref节点
        cacheRefElement(context.evalNode("cache-ref"));
        // 解析cache节点
        cacheElement(context.evalNode("cache"));
        // 解析parameterMap节点
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // 解析resultMap节点
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // 解析sql节点
        sqlElement(context.evalNodes("/mapper/sql"));
        // 解析 select insert update delete 节点
        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);
    }
}

3.1.1 cache-ref标签解析

cache-ref标签可以为当前mapper文件引入其他mapper的二级缓存,这个标签的解析逻辑如下:

private void cacheRefElement(XNode context) {
    if (context != null) {
        // Configuration.cacheRefMap属性中添加数据   当前命名空间和节点的namespace属性进行关联
        configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
        // 创建CacheRefResolver对象
        CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
        try {
            // 解析cacheRef
            cacheRefResolver.resolveCacheRef();
        } catch (IncompleteElementException e) {
            // 如果解析发生异常添加到incompleteCacheRef字段中
            configuration.addIncompleteCacheRef(cacheRefResolver);
        }
    }
}

上面的逻辑会调用CacheRefResolver相关的接口,这个类的源码如下:

public class CacheRefResolver {
    // Mapper解析辅助类
    private final MapperBuilderAssistant assistant;
    // cache-ref节点的namespace属性值
    private final String cacheRefNamespace;
​
    public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
        this.assistant = assistant;
        this.cacheRefNamespace = cacheRefNamespace;
    }
​
    public Cache resolveCacheRef() {
        return assistant.useCacheRef(cacheRefNamespace);
    }
}
​
public Cache useCacheRef(String namespace) {
    if (namespace == null) {
        throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
        unresolvedCacheRef = true;
        // 从configuration中通过命名空间获取Cache
        Cache cache = configuration.getCache(namespace);
        // 如果不存在抛出异常
        if (cache == null) {
            throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
        }
        currentCache = cache;
        unresolvedCacheRef = false;
        return cache;
    } catch (IllegalArgumentException e) {
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    }
}

3.1.2 cache标签解析

cache标签是用来配置二级缓存的,这个标签的解析我们在上篇文章中进行介绍过,其源码如下:

private void cacheElement(XNode context) {
    if (context != null) {
        // 获取type属性  默认为PERPETUAL
        String type = context.getStringAttribute("type", "PERPETUAL");
        // 通过别名获取对应的类型
        Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        // 获取eviction属性  默认为LRU
        String eviction = context.getStringAttribute("eviction", "LRU");
        // 获取eviction别名对应的类型
        Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
        // 获取flushInterval属性
        Long flushInterval = context.getLongAttribute("flushInterval");
        // 获取size属性
        Integer size = context.getIntAttribute("size");
        // 获取redaOnly属性  默认为false
        boolean readWrite = !context.getBooleanAttribute("readOnly", false);
        // 获取blocking属性  默认为false
        boolean blocking = context.getBooleanAttribute("blocking", false);
        // 获取子节点数据
        Properties props = context.getChildrenAsProperties();
        // 创建Cache对象并添加到Configuration.caches中
        builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
}
​
public Cache useNewCache(Class<? extends Cache> typeClass,
                         Class<? extends Cache> evictionClass,
                         Long flushInterval,
                         Integer size,
                         boolean readWrite,
                         boolean blocking,
                         Properties props) {
    // 创建Cache对象
    Cache cache = new CacheBuilder(currentNamespace)// namespace作为缓存id
        .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;
}

通过这个代码我们发现,cachecache-ref节点的解析最后都会修改MapperBuilderAssistant.currentCache属性的值,因此,当这两个标签都存在时会存在覆盖的问题。

3.1.3 parameterMap标签解析

通过官方文档我们看到这个标签已经被废弃了,这里我们就不再进行说明了。

image-20210918145632014

3.1.4 resultMap节点解析

在介绍这个标签前我们先看看resultMap节点下都可能存在哪些节点,从官网复制个例子出来如下:

image-20210918151424884

Mybatis对这个节点的解析,会将一条条的映射关系解析成一个个的ResultMapping对象,将整个resultMap解析成ResultMap对象。 ResultMapping的属性如下:

// Configuration对象
private Configuration configuration;
// property属性
private String property;
// column属性
private String column;
// 字段对应的Java类型 javaType属性指定
private Class<?> javaType;
// 字段对应的jdbc类型  jdbcType属性指定
private JdbcType jdbcType;
// 类型转换器   typeHandler属性
private TypeHandler<?> typeHandler;
// 这个是通过resultMap属性解析出的值
private String nestedResultMapId;
// select属性解析出的值
private String nestedQueryId;
// notNullColumn属性解析的值
private Set<String> notNullColumns;
// columnPrefix属性
private String columnPrefix;
// 标记  用来记录是否为 constructor和id节点
private List<ResultFlag> flags;
private List<ResultMapping> composites;
// resultSet属性
private String resultSet;
// foreignColumn属性
private String foreignColumn;
// 是否懒加载
private boolean lazy;

ResultMap的属性如下:

// Configuration对象
private Configuration configuration;
// resultMap节点的id属性
private String id;
// resultMap节点的type属性
private Class<?> type;
// resultMapping列表 不包含discriminator节点解析的ResultMapping
private List<ResultMapping> resultMappings;
// 映射关系中带有id节点 resultMapping
private List<ResultMapping> idResultMappings;
// constructor节点的resultMapping
private List<ResultMapping> constructorResultMappings;
private List<ResultMapping> propertyResultMappings;
// column属性列表
private Set<String> mappedColumns;
// properties属性列表
private Set<String> mappedProperties;
// discriminator节点解析出的数据
private Discriminator discriminator;
// 是否有resultMap属性
private boolean hasNestedResultMaps;
// 是否有select属性
private boolean hasNestedQueries;
private Boolean autoMapping;

解析逻辑源码如下:

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对象
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
        typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    // ResultMapping列表  会将子节点解析成一个个的ResultMapping
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
        // 解析constructor节点   这个节点用于配置构造方法所需的属性
        if ("constructor".equals(resultChild.getName())) {
            // 解析constructor节点的子节点
            processConstructorElement(resultChild, typeClass, resultMappings);
        } else if ("discriminator".equals(resultChild.getName())) {
            // 解析discriminator节点
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
        } else {
            // 标记列表
            List<ResultFlag> flags = new ArrayList<>();
            if ("id".equals(resultChild.getName())) {
                // 如果是id标签加上ResultFlag.ID标签
                flags.add(ResultFlag.ID);
            }
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
    }
    // 获取id属性
    String id = resultMapNode.getStringAttribute("id",
                                                 resultMapNode.getValueBasedIdentifier());
    // 获取extends属性
    String extend = resultMapNode.getStringAttribute("extends");
    // 获取autoMapping属性
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    // 创建ResultMapResolver对象
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
        // 创建ResultMap对象并返回
        return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
        // 如果解析失败,将resultMapResolver添加到Configuration.incompleteResultMap中  在后面进行处理
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
    }
}

processConstructorElement方法用来解析constructor节点,逻辑如下:

private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
    List<XNode> argChildren = resultChild.getChildren();
    // 遍历子节点
    for (XNode argChild : argChildren) {
        // 标记列表
        List<ResultFlag> flags = new ArrayList<>();
        // 添加CONSTRUCTOR标记
        flags.add(ResultFlag.CONSTRUCTOR);
        if ("idArg".equals(argChild.getName())) {
            // 如果是idArg节点  添加ID标记
            flags.add(ResultFlag.ID);
        }
        // 解析出子节点对应的ResultMapping对象
        resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
}

processDiscriminatorElement方法用来解析discriminator节点,逻辑如下:

private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) {
    // 获取column属性
    String column = context.getStringAttribute("column");
    // 获取javaType属性
    String javaType = context.getStringAttribute("javaType");
    // 获取jdbcType属性
    String jdbcType = context.getStringAttribute("jdbcType");
    // 获取typeHandler属性
    String typeHandler = context.getStringAttribute("typeHandler");
    // 获取javaType对应的Class对象
    Class<?> javaTypeClass = resolveClass(javaType);
    // 获取typeHandler对应的Class对象
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    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);
    }
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}

buildResultMappingFromContext方法是用来创建ResultMapping对象,逻辑如下:

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
        // 如果是Constructor节点下的节点  获取name属性
        property = context.getStringAttribute("name");
    } else {
        // 获取property属性
        property = context.getStringAttribute("property");
    }
    // 获取column属性
    String column = context.getStringAttribute("column");
    // 获取javaType属性
    String javaType = context.getStringAttribute("javaType");
    // 获取jdbcType属性
    String jdbcType = context.getStringAttribute("jdbcType");
    // 获取select属性
    String nestedSelect = context.getStringAttribute("select");
    // 获取resultMap属性
    String nestedResultMap = context.getStringAttribute("resultMap", () ->
                                                        processNestedResultMappings(context, Collections.emptyList(), resultType));
    // 获取notNullColumn属性
    String notNullColumn = context.getStringAttribute("notNullColumn");
    // 获取columnPrefix属性
    String columnPrefix = context.getStringAttribute("columnPrefix");
    // 获取typeHandler属性
    String typeHandler = context.getStringAttribute("typeHandler");
    // 获取resultSet属性
    String resultSet = context.getStringAttribute("resultSet");
    // 获取foreignColumn属性
    String foreignColumn = context.getStringAttribute("foreignColumn");
    // 是否支持懒加载
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    // 解析出javaType对应的Class对象
    Class<?> javaTypeClass = resolveClass(javaType);
    // 解析出typeHandler对应的Class对象
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    // 获取到对应的JdbcType
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // 创建ResultMapping对象
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}

解析完resultMapping后会创建ResultMapResolver对象来创建ResultMap并添加到Configuration.resultMaps属性中,逻辑如下:

public class ResultMapResolver {
    private final MapperBuilderAssistant assistant;
    private final String id;
    private final Class<?> type;
    private final String extend;
    private final Discriminator discriminator;
    private final List<ResultMapping> resultMappings;
    private final Boolean autoMapping;
​
    public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
        this.assistant = assistant;
        this.id = id;
        this.type = type;
        this.extend = extend;
        this.discriminator = discriminator;
        this.resultMappings = resultMappings;
        this.autoMapping = autoMapping;
    }
​
    public ResultMap resolve() {
        return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
    }
​
}
​
public ResultMap addResultMap(
    String id,
    Class<?> type,
    String extend,
    Discriminator discriminator,
    List<ResultMapping> resultMappings,
    Boolean autoMapping) {
    // 在id属性前面配置上namespace.作为id
    id = applyCurrentNamespace(id, false);
    extend = applyCurrentNamespace(extend, true);
​
    if (extend != null) {
        // extend不为空  判断对应的resultMap是否存在
        if (!configuration.hasResultMap(extend)) {
            throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
        }
        // 获取extend对应的ResultMap
        ResultMap resultMap = configuration.getResultMap(extend);
        // 获取对应的ResultMapping列表
        List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
        // 移除当前resultMap中包含的ResultMapping
        extendedResultMappings.removeAll(resultMappings);
        // Remove parent constructor if this resultMap declares a constructor.
        boolean declaresConstructor = false;
        // 判断当前ResultMapping中是否存在CONSTRUCTOR标签的
        for (ResultMapping resultMapping : resultMappings) {
            if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                declaresConstructor = true;
                break;
            }
        }
        // 如果当前ResultMap中存在CONSTRUCTOR标签的ResultMapping  移除扩展ResultMap中含有CONSTRUCTOR标记的ResultMapping
        if (declaresConstructor) {
            extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
        }
        // 添加扩展ResultMapping
        resultMappings.addAll(extendedResultMappings);
    }
    // 创建ResultMap
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    // 添加到Configuration.resultMaps属性中
    configuration.addResultMap(resultMap);
    return resultMap;
}

3.1.5 sql节点解析

sql的节点解析是添加到sqlFragments属性中,其源码如下:

private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        String databaseId = context.getStringAttribute("databaseId");
        // 获取id属性值
        String id = context.getStringAttribute("id");
        // 在id属性值前加上namespace
        id = builderAssistant.applyCurrentNamespace(id, false);
        // 校验databaseId是否匹配
        if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
            // 存储到sqlFragments中
            sqlFragments.put(id, context);
        }
    }
}

3.1.6 insert update delete select节点 解析

buildStatementFromContext是解析我们写的SQL语句节点的入口,这个方法的逻辑如下:

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) {
        // 创建XMLStatementBuilder对象
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            // 解析StatementNode
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

通过上面的逻辑我们发现最终的解析逻辑调用的是XMLStatementBuilder中的方法。接下来我们看看这个类的解析过程

3.1.7 XMLStatementBuilder

XMLStatmentBuilder也是BaseBuilder的一个实现类,通过上面的源码我们知道XMLStatmentBuilder.parseStatementNode()方法是解析SQL语句的入口,其逻辑如下:

public void parseStatementNode() {
    // 获取id属性
    String id = context.getStringAttribute("id");
    // 获取databaseId属性
    String databaseId = context.getStringAttribute("databaseId");
    // 获取到的databaseId和创建时设置的databaseId不一致   不处理
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }
    // 获取节点名称
    String nodeName = context.getNode().getNodeName();
    // 获取sql类型
    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);
​
    // Include Fragments before parsing
    // 处理include节点
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());
    // 获取parameterType属性
    String parameterType = context.getStringAttribute("parameterType");
    // 获取parameterType类型
    Class<?> parameterTypeClass = resolveClass(parameterType);
    // 获取lang属性
    String lang = context.getStringAttribute("lang");
    // 获取LanguageDriver对象
    LanguageDriver langDriver = getLanguageDriver(lang);
​
    // Parse selectKey after includes and remove them.
    // selectKey节点
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
​
    // 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);
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                                                   configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
            ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
​
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    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");
​
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                                        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                                        resultSetTypeEnum, flushCache, useCache, resultOrdered,
                                        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

这段代码看着很长,大部分内容是来获取相应的属性值,刨除这些内容,主要的流程如下:

  • 处理include节点
  • 创建LanguageDriver对象
  • 创建SqlSource对象
  • 创建MappedStatement对象

处理include数据的入口为XMLIncludeTransformer.applyIncludes方法,逻辑如下:

public void applyIncludes(Node source) {
    Properties variablesContext = new Properties();
    // 获取配置文件中的配置内容
    Properties configurationVariables = configuration.getVariables();
    Optional.ofNullable(configurationVariables).ifPresent(variablesContext::putAll);
    applyIncludes(source, variablesContext, false);
}
​
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
    // 判断是否为include节点
    if ("include".equals(source.getNodeName())) {
        // 查找refid对应的sql语句
        Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
        // 获取子节点中的name和value属性组成Properties对象   会替换value中的占位符
        Properties toIncludeContext = getVariablesContext(source, variablesContext);
        // 递归处理
        applyIncludes(toInclude, toIncludeContext, true);
        if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
            toInclude = source.getOwnerDocument().importNode(toInclude, true);
        }
        source.getParentNode().replaceChild(toInclude, source);
        while (toInclude.hasChildNodes()) {
            toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
        }
        toInclude.getParentNode().removeChild(toInclude);
    } else if (source.getNodeType() == Node.ELEMENT_NODE) {
        if (included && !variablesContext.isEmpty()) {
            // replace variables in attribute values
            NamedNodeMap attributes = source.getAttributes();
            for (int i = 0; i < attributes.getLength(); i++) {
                Node attr = attributes.item(i);
                attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
            }
        }
        // 获取子节点
        NodeList children = source.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            // 处理子节点
            applyIncludes(children.item(i), variablesContext, included);
        }
    } else if (included && (source.getNodeType() == Node.TEXT_NODE || source.getNodeType() == Node.CDATA_SECTION_NODE)
               && !variablesContext.isEmpty()) {
        // replace variables in text node
        // 替换其中的占位符
        source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
    }
}

获取LanguageDriver的获取逻辑比较简单哈,默认情况下返回的是Configuration.languageRegistry.defaultDriver,这个值的设置我们可以在Configuration类的构造方法中看到,默认是XMLLanguageDriver

获取到LanguageDriver对象后会调用这个类的createSqlSource方法获取SqlSource对象,逻辑如下:

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    // 创建XMLScriptBuilder对象
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    // 调用XMLScriptBuilder.parseScriptNode方法创建SqlSource对象
    return builder.parseScriptNode();
}

XMLScriptBuilder.parseScriptNode()方法逻辑如下:

public SqlSource parseScriptNode() {
    // 判断是否为动态sql并获取MixedSqlNode
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    // 判断是否为动态sql
    if (isDynamic) {
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}
​
protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
    // 获取子标签
    NodeList children = node.getNode().getChildNodes();
    // 遍历子标签
    for (int i = 0; i < children.getLength(); i++) {
        // 创建XNode对象
        XNode child = node.newXNode(children.item(i));
        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
            String data = child.getStringBody("");
            TextSqlNode textSqlNode = new TextSqlNode(data);
            // 判断是否为动态sql  包含${}判定为动态sql
            if (textSqlNode.isDynamic()) {
                contents.add(textSqlNode);
                isDynamic = true;
            } else {
                contents.add(new StaticTextSqlNode(data));
            }
        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
            // 如果是动态sql标签则是动态sql
            String nodeName = child.getNode().getNodeName();
            // 通过节点名称获取对应的NodeHandler对象
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            // NodeHandler不存在  抛出异常
            if (handler == null) {
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            // 会创建对应的SqlNode对象添加到列表中
            handler.handleNode(child, contents);
            // 标记为是动态sql
            isDynamic = true;
        }
    }
    // 创建MixedSqlNode对象并返回
    return new MixedSqlNode(contents);
}

创建MappedStatement对象,这个是调用了MapperBuilderAssistant.addMappedStatement方法,将创建的对象存储到Configuration.mappedStatements属性中,这里不进行粘贴了。

通过这部分代码我们能够发现,我们在Mapper映射文件中定义的select insert update delete节点会被转换成MappedStatement对象,这个节点中写的sql会被转换成SqlSource对象,动态sql会被转换成SqlNode对象。

3.2 绑定Mapper

当解析完成映射文件后,会调用bindMapperForNamespace方法,逻辑如下:

private void bindMapperForNamespace() {
    // 获取当前namespace
    String namespace = builderAssistant.getCurrentNamespace();
    // 如果namespace不为空
    if (namespace != null) {
        Class<?> boundType = null;
        try {
            // 获取namespace对应的Class对象
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
            // ignore, bound type is not required
        }
        // 如果没有进行绑定
        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
            // loadedResource中添加数据
            configuration.addLoadedResource("namespace:" + namespace);
            // 注册mapper
            configuration.addMapper(boundType);
        }
    }
}

3.3 parsePending*方法

通过上面的方法源码,我们发现当解析resultMapcache-refstatement失败后会添加到相对应的集合中,这里面的逻辑比较简单,大家自行查看吧。

在这里我们梳理下同时配置cachecache-ref的覆盖逻辑,如下:

  • 解析cache-ref,解析成功会设置当前缓存对象,解析失败会添加到一个集合中。
  • 解析cache标签时如果上一步成功的解析,这个标签的解析会覆盖掉上步的缓存。
  • 处理解析失败列表,覆盖掉上步解析的缓存对象。

3.4 注解方法的解析

相信大家都知道Mybats除了映射文件的方式,还支持我们使用注解进行SQL编写,那么MybatisMapper上的注解是在那里解析的呢?我们在这部分看一下。

大家是否还记得,当我们使用MapperRegistry注册Mapper后,会调用MapperAnnotationBuilder.parse方法,这个咱们在前面的章节跳过了没有说明,这个便是解析Mapper注解的入口,其逻辑如下:

public void parse() {
    // Mapper全限定名
    String resource = type.toString();
    // 如果已经处理过则不处理
    if (!configuration.isResourceLoaded(resource)) {
        // 加载映射文件
        loadXmlResource();
        configuration.addLoadedResource(resource);
        // 设置当前命名空间
        assistant.setCurrentNamespace(type.getName());
        // 解析cache  @CacheNamespace注解
        parseCache();
        // 解析cache-ref  @CacheNamespaceRef注解
        parseCacheRef();
        // 获取类型中的方法
        for (Method method : type.getMethods()) {
            if (!canHaveStatement(method)) {
                continue;
            }
            // 解析ResultMap
            if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                && method.getAnnotation(ResultMap.class) == null) {
                parseResultMap(method);
            }
            try {
                // 解析statement
                parseStatement(method);
            } catch (IncompleteElementException e) {
                // 解析失败添加到incompleteMethod中
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    // 处理解析失败的方法
    parsePendingMethods();
}

其他的代码大家自行查看吧。

4 总结

今天的内容到这里就结束了,今天我们详细的介绍了下Mybatis的配置解析相关的内容,篇幅较长,需要大家耐心的查看,在本篇文章中设计到比较重要的类如下:

  • XMLConfigBuilder 解析Mybatis配置文件
  • XMLMapperBuilder 解析Mapper映射文件
  • XMLStatementBuilder 解析Mapper映射文件中的insert update delete select节点
  • MapperBuilderAssistant 解析Mapper映射文件的辅助类
  • XMLScriptBuilder 解析SQL语句 处理动态SQL
  • MappedStatement 用来表示insert update delete select节点的数据
  • ResultMapping 存储resultMap节点下的子节点 一个子节点会封装成一个该对象
  • ResultMap resultMap对应的Java
  • SqlSource 用来表示一条SQL语句
  • SqlNode 用来表示语句中的动态SQL

希望大家通过本文能对Mybatis的配置解析流程有一个大概的了解,能够知道Mybatis在这个阶段都做了哪些工作。

欢迎关注公众号:Bug搬运小能手