MyBatis 通过应用 SqlSessionFactoryBuilder ,以创立 SqlSessionFactory 对象来进行 MyBatis 的启动,无论是否集成 Spring 的模块。
SqlSessionFactoryBuilder 蕴含以下办法:

  • SqlSessionFactoryBuilder#build(java.io.Reader)
  • SqlSessionFactoryBuilder#build(java.io.Reader,java.lang.String)
  • SqlSessionFactoryBuilder#build(java.io.Reader,java.util.Properties)
  • SqlSessionFactoryBuilder#build(java.io.Reader,java.lang.String,java.util.Properties)
  • SqlSessionFactoryBuilder#build(java.io.InputStream)
  • SqlSessionFactoryBuilder#build(java.io.InputStream,java.lang.String)
  • SqlSessionFactoryBuilder#build(java.io.InputStream,java.util.Properties)
  • SqlSessionFactoryBuilder#build(java.io.InputStream,java.lang.String,java.util.Properties)
  • SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)

除了最初了一个须要传入 Configuration 参数的办法外,其余底层都是通过 XMLConfigBuilder#parse() 办法来启动 MyBaits 。
起因也很简略,除了须要传入 Configuration 参数的办法外,其余的办法都是要传入 Reader 或者 InputStream 文件流参数来进行文件读写。
XMLConfigBuilder 类是用来读取 mybatis-config.xml 文件的来进行 MyBatis 的全局配置和启动,那么它天然就须要通过文件流参数来读取配置文件。

SqlSessionFactoryBuilder 源码

/** * 暗藏了局部源码,只保留次要局部 */public class SqlSessionFactoryBuilder {    /**     * 通过第一个参数的 Reader 对象读取 MyBatis-config.xml 文件,进行 MyBatis 的初始化,并返回 SqlSessionFactory 对象。     * SqlSessionFactoryBuilder 除了该办法之外,还有另外一个办法反对 InputStream 文件流来读取 MyBatis-config.xml 文件。     */    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {        try {            // 创立 XMLConfigBuilder 对象            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);            // 调用 XMLConfigBuilder#parse() 办法,该办法进行 MyBatis 的初始化工作。            // 最终失去了 Configuration 对象,并调用 build(Configuration) 办法,返回 SqlSessionFactory 的默认实现。            return build(parser.parse());        } catch (Exception e) {            throw ExceptionFactory.wrapException("Error building SqlSession.", e);        } finally {            ErrorContext.instance().reset();            try {                reader.close();            } catch (IOException e) {            }        }    }    public SqlSessionFactory build(Configuration config) {        return new DefaultSqlSessionFactory(config);    }}

XMLConfigBuilder

SqlSessionFactoryBuilder#build(Reader, String, Properties) 办法中,咱们看到它实质上是通过 XMLConfigBuilder 对象来实现 MyBatis 的初始化,那么 XMLConfigBuilder#parse() 办法是咱们要关注的内容。
它次要干了以下几个事件:

  • 解析 <properties> 标签;
  • 解析 <settings> 标签;解析过程中如果蕴含了 vfsImpl 的属性,那么去设置自定义的 vfs 实现类。
  • 解决日志相干组件;
  • 解析 <typeAliases> 标签;
  • 解析 <plugins> 标签;
  • 解析 <objectFactory> 标签;
  • 解析 <objectWrapperFactory> 标签;
  • 解析 <reflectorFactory> 标签;
  • 解析 <environments> 标签;
  • 解析 <databaseIdProvider> 标签;
  • 解析 <typeHandlers> 标签;
  • 解析 <mappers> 标签。

源码如下:

public class XMLConfigBuilder extends BaseBuilder {    // configuration、typeAliasRegistry、typeHandlerRegistry 都在父类 BaseBuilder 中    /**    * Mybatis 初始化解析到的配置信息,都会记录到 configuration 这里    * Mybatis 的初始化过程,根本都是围绕这个对象的    * 这个能够了解成是这个单例的对象    */    protected final Configuration configuration;    /**    * 别名注册核心,用于解析别名    */    protected final TypeAliasRegistry typeAliasRegistry;    /**    * TypeHandler 注册核心,用于解析 TypeHandler    * TypeHandler 次要是做 Java 类型和数据库类型的转换映射    * 而用户能够自定义 TypeHandler , 自定义的 TypeHandler 都会记录在这里    */    protected final TypeHandlerRegistry typeHandlerRegistry;    /**    * 标识是否曾经齐全解析完 mybatis-config.xml 配置文件    */    private boolean parsed;    /**    * XML 解析器,用于解析 mybatis-config.xml 配置文件    */    private final XPathParser parser;    /**    * 标签定义的环境名称    */    private String environment;    /**    * 对 Reflector 对象的创立和缓存    */    private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();    public Configuration parse() {        if (parsed) {            throw new BuilderException("Each XMLConfigBuilder can only be used once.");        }        parsed = true;        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"));            // 从 <settings> 标签中,查找并设置自定义的 vfs(虚构文件系统) 实现类。            loadCustomVfs(settings);            // 解决日志相干组件            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"));            // 从 <settings> 标签中解析到的内容,设置到 Configuration 对象的字段上            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);        }    }}

解析 properties 标签

通过调用 propertiesElement(XNode) 办法,读取 <properties> 标签以及它的属性值。
<properties> 标签读取属性的优先级如下:通过办法参数传递 > resource 或者 url 属性指定的配置文件 > properties 元素指定的属性。
<properties> 标签中解析进去的 KV 信息会被记录到一个 Properties 对象(也就是 Configuration 全局配置对象的 variables 字段),在后续解析其余标签的时候,MyBatis 会应用这个 Properties 对象中记录的 KV 信息替换匹配的占位符。

解析 settings 标签

MyBatis 在解析 <settings> 标签时,次要是这么做:

  1. 通过调用 settingsAsProperties(XNode) 办法来解析 <settings> 标签,并失去 Properties 对象
  2. 通过调用 loadCustomVfs(Properties) 办法来查找并设置自定义的 vfs 实现
  3. 通过调用 loadCustomLogImpl(Properties) 办法来查找并设置自定义的日志实现
  4. 通过调用 settingsElement(Properties) 办法,来设置 Configuration 对象的信息

解析 typeAliases 标签

<typeAliases> 标签扫描的类 class 和别名信息,保留到 TypeAliasRegistry.typeAliases 的字段中去。
源码如下:

private void typeAliasesElement(XNode parent) {    if (parent != null) {        for (XNode child : parent.getChildren()) {            if ("package".equals(child.getName())) {                // 如果是 <package> 标签,则通过 name 属性来扫描所有的 class                // 而后查看每一个 class 是否蕴含 @Alias 注解。如果有,那么 class 的别名就是 @Alias 的值,否则就是 class 的名字                // 最初保留到 TypeAliasRegistry.typeAliases 的字段中去                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) {                        // 如果 alias 属性为 null,就先从 type 属性指定的 class 中,查找是否有 @Alias 这个注解                        // 如果注解存在,则拿注解的值,否则就间接拿 class 的名字                        // 最初保留到 TypeAliasRegistry.typeAliases 的字段中去                        typeAliasRegistry.registerAlias(clazz);                    } else {                        // 如果 alias 属性不为 null,就去将 alias 的值设置为 class 的别名                        // 最初保留到 TypeAliasRegistry.typeAliases 的字段中去                        typeAliasRegistry.registerAlias(alias, clazz);                    }                } catch (ClassNotFoundException e) {                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);                }            }        }    }}

解析 plugins 标签

扫描 <plugins> 下的 <plugin> 标签,并将这些 <plugin> 标签对应的 class ,通过无参构造方法来创立进去。增加到 Configuration 对象的 interceptorChain 成员变量(外部保护了 List<Interceptor> 对象的链) 下。

解决 typeHandlers 标签

源码如下:

private void typeHandlerElement(XNode parent) {    if (parent != null) {        // 解决所有 <typeHandler> 的标签        for (XNode child : parent.getChildren()) {            if ("package".equals(child.getName())) {                // 如果指定了 package 属性,就扫描指定包中所有带有 @MappedTypes 注解的类,而后实现 TypeHandler 的注册                String typeHandlerPackage = child.getStringAttribute("name");                typeHandlerRegistry.register(typeHandlerPackage);            } else {                // 如果没有指定 package 属性,则尝试获取 javaType、jdbcType、handler 三个属性                String javaTypeName = child.getStringAttribute("javaType");                String jdbcTypeName = child.getStringAttribute("jdbcType");                String handlerTypeName = child.getStringAttribute("handler");                // 依据属性确定 TypeHandler 类型以及它可能解决的数据库类型和 Java 类型                Class<?> javaTypeClass = resolveClass(javaTypeName);                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);                Class<?> typeHandlerClass = resolveClass(handlerTypeName);                // 调用 TypeHandlerRegistry 的 register 办法来注册 TypeHandler                if (javaTypeClass != null) {                    if (jdbcType == null) {                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);                    } else {                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);                    }                } else {                    typeHandlerRegistry.register(typeHandlerClass);                }            }        }    }}

解析 mappers 标签

Mybatis 通过解析 <mappers> 标签,加载并增加 Mapper 到 Configuration 对象的 MapperRegistry 成员变量中去。
它的工作流程如下:

  1. 扫描 <mappers> 标签下的 <mapper> 标签,依据其 packageresourceurl 或者 mapperClass 属性的值,创立出 XMLMapperBuilder
  2. 调用 XMLMapperBuilder#parse() 办法,以实现 <mappers> 标签的解析。

源码如下:

// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElementprivate void mapperElement(XNode parent) throws Exception {    if (parent != null) {        // 遍历每个子标签        for (XNode child : parent.getChildren()) {            if ("package".equals(child.getName())) {                // 如果指定了 <package> 标签,那么就去扫描指定包内的全副 class                String mapperPackage = child.getStringAttribute("name");                configuration.addMappers(mapperPackage);            } else {                // 解析 <mapper> 子标签,这里会获取 resource、url、class 三个属性,这三个属性互斥                String resource = child.getStringAttribute("resource");                String url = child.getStringAttribute("url");                String mapperClass = child.getStringAttribute("class");                /*                如果 <mapper> 标签指定了 resource 或者 url 属性,就会创立 XMLMapperBuilder 对象                而后应用这个 XMLMapperBuilder 实例解析指定的 Mapper.xml 配置文件                Mybatis 会为每个 mapper.xml 文件创建一个 XMLMapperBuilder                */                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) {                    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) {                    // 如果 <mapper> 标签指定了 class 属性,则向 MapperRegistry 注册 class 属性指定的 Mapper 接口                    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#parse() 办法

XMLMapperBuilder#parse() 办法的工作流程如下:

  1. 解析 <cache-ref><cache> 标签的信息,来设置该 Mapper 的二级缓存
  2. 解析 <parameterMap> 标签
  3. 解析 <resultMap> 标签
  4. 解析 <sql> 标签
  5. 解析 <select><insert><update><delete> 标签

源码如下:

// org.apache.ibatis.builder.xml.XMLMapperBuilder#parsepublic void parse() {    // 这么做次要是避免屡次加载同一个 mapper 的配置信息(因为 Spring 早就曾经加过一次了)    if (!configuration.isResourceLoaded(resource)) {        // 真正解析 Mapper.xml 映射文件的中央        configurationElement(parser.evalNode("/mapper"));        // 将 resource 对象增加到 Configuration 中,示意曾经加载过该资源了        configuration.addLoadedResource(resource);        // 从 MapperBuilderAssistant 中获取以后加载 Mapper 的全限定名        // 并通过全限定名找到该 Mapper 的 class 信息,而后增加到 Configuration.mapperRegistry 的 knownMappers 中去        bindMapperForNamespace();    }    parsePendingResultMaps();    parsePendingCacheRefs();    parsePendingStatements();}// org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElementprivate void configurationElement(XNode context) {    try {        // 获取 mapper 的 namespace        String namespace = context.getStringAttribute("namespace");        if (namespace == null || namespace.isEmpty()) {            throw new BuilderException("Mapper's namespace cannot be empty");        }        // 设置 mapper 的 namespace 到 builderAssistant 的 currentNamespace 字段上        // 后续解析 <cache-ref> 标签会用到        builderAssistant.setCurrentNamespace(namespace);        // 解析 cache 和 cache-ref(二级缓存) 标签        cacheRefElement(context.evalNode("cache-ref"));        cacheElement(context.evalNode("cache"));        // 解析 parameterMap 和 resultMap 标签        parameterMapElement(context.evalNodes("/mapper/parameterMap"));        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);    }}

1 解析 cache-ref 和 cache 标签

解析 <cache-ref> 标签的流程如下:

  1. 将以后 mapper 的 namespace 与 <cache-ref> 的 namespace 属性关联到 configuration.cacheRef 中去
  2. Configuration.caches 中查找 <cache-ref> 指定的缓存,并通过 MapperBuilderAssistant 将以后的 mapper 的缓存设置成 <cache-ref> 指定的缓存。
  3. 如果 <cache-ref> 指定的缓存没有加载进来的话,那么就会抛出 IncompleteElementException 的异样,
    并增加到 configurationincompleteCacheRefs 列表中去,等到后续再增加

<cache> 标签的解析绝对 <cache-ref> 就简略很多了,流程如下:

  1. 读取 <cache> 标签下的 typeevictionflushInterval 等属性的信息
  2. 依据读取到的属性,用于创立一个新的 Cache 缓存对象,而后增加到 Configuration.caches 中去

2 解析 parameterMap 标签

流程如下:

  1. 获取 <parameterMap> 标签的 idtype 属性。
  2. 解析 <parameterMap> 标签内所有的 <parameter> 标签的属性,如 property , javaType , jdbcType , resultMap 等属性。
  3. <parameter> 标签解析到的属性,通过 builderAssistant#buildParameterMapping() 办法创立 ParameterMapping 对象。
  4. 创立 ParameterMap 对象,并将 ParameterMapping 对象增加到 ParameterMap 外面。
  5. ParameterMap 对象增加到 Configuration 对象的 parameterMap 中去。

源码如下:

private void parameterMapElement(List<XNode> list) {    for (XNode parameterMapNode : list) {        // 解析每个 <parameterMap> 标签的 id 、type 属性        String id = parameterMapNode.getStringAttribute("id");        String type = parameterMapNode.getStringAttribute("type");        Class<?> parameterClass = resolveClass(type);        List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");        List<ParameterMapping> parameterMappings = new ArrayList<>();        for (XNode parameterNode : parameterNodes) {            // 解析 <parameter> 的标签属性            String property = parameterNode.getStringAttribute("property");            String javaType = parameterNode.getStringAttribute("javaType");            String jdbcType = parameterNode.getStringAttribute("jdbcType");            String resultMap = parameterNode.getStringAttribute("resultMap");            String mode = parameterNode.getStringAttribute("mode");            String typeHandler = parameterNode.getStringAttribute("typeHandler");            Integer numericScale = parameterNode.getIntAttribute("numericScale");            ParameterMode modeEnum = resolveParameterMode(mode);            Class<?> javaTypeClass = resolveClass(javaType);            JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);            Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);            // 依据 <parameter> 标签的属性,创立 ParameterMapping 对象,并增加到 Configuration 的 parameterMap 中去            ParameterMapping parameterMapping =                 builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);            parameterMappings.add(parameterMapping);        }        builderAssistant.addParameterMap(id, parameterClass, parameterMappings);    }}

2.1 创立 ParameterMapping 对象流程

MyBatis 在解析 <parameter> 的过程中,会创立 ParameterMapping 对象进去,用来示意 <parameter> 标签的信息。
ParameterMapping 对象创立的流程如下:

  1. 生成 resultMap 的名字,名字是 mapper 的全限定名+ resultMap 的 id,用于示意该 PameterMapping 对象与 resultMap 的关联关系。
  2. 解析 <parameter> 标签对应 javaType ,也就是参数的 class 信息,它的流程如下:

    1. 如果 javaType 参数不为空,那么 javaTypeClass 也就是 javaType
    2. 如果 jdbcType 参数的值为 JdbcType.CURSOR,那么 javaTypeClass 的值就是它
    3. 如果 parameterType 参数的值为 Map ,那么 javaTypeClass 的值就是 Object.class
    4. 尝试通过 parameterType 参数来创立 MetaClass , 并通过 MetaClass 来获取 property 的 class 信息,那么 javaTypeClass 就是这个 class 信息。
      实质就是通过反射来获取 parameterType 参数的 property 参数的 class 信息
    5. 如果都获取不到,那返回 Object.class
  3. 依据 typeHandler 的 class 信息来获取/创立 typeHandler 对象
  4. 返回 ParameterMapping 对象

源码如下:

//org.apache.ibatis.builder.MapperBuilderAssistant#buildParameterMappingpublic ParameterMapping buildParameterMapping(    Class<?> parameterType,    String property,    Class<?> javaType,    JdbcType jdbcType,    String resultMap,    ParameterMode parameterMode,    Class<? extends TypeHandler<?>> typeHandler,    Integer numericScale) {    // 生成 resultMap 的名字    resultMap = applyCurrentNamespace(resultMap, true);    // 解析参数的 javaType ,用于决定 <parameter> 的类型    Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);    // 依据 typeHandler 的 class 信息来获取/创立 typeHandler 对象    TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);    return new ParameterMapping.Builder(configuration, property, javaTypeClass)        .jdbcType(jdbcType)        .resultMapId(resultMap)        .mode(parameterMode)        .numericScale(numericScale)        .typeHandler(typeHandlerInstance)        .build();}/*** 解析参数的 javaType* 如果 javaType 参数为空的话,则尝试通过 jdbcType 或者 resultType 去查找参数的类型* @param resultType <parameterMap> 的 type 信息,也就是 parameterMap 映射的类* @param property <parameter> 的 property 信息,也就是 parameterMap 映射的类的字段名* @param javaType <parameter> 的 javaType ,示意指定的字段名的类型* @param jdbcType <parameter> 的 jdbcType ,示意指定的字段名对应的 jdbcType* @return*///org.apache.ibatis.builder.MapperBuilderAssistant#resolveParameterJavaTypeprivate Class<?> resolveParameterJavaType(Class<?> resultType, String property, Class<?> javaType, JdbcType jdbcType) {    if (javaType == null) {        // 如果没有指定 javaType 的话,则思考从 jdbcType 或者 resultType 外面获取        if (JdbcType.CURSOR.equals(jdbcType)) {            javaType = java.sql.ResultSet.class;        } else if (Map.class.isAssignableFrom(resultType)) {            javaType = Object.class;        } else {            // 如果 jdbcType 不是 CURSOR ,resultType 也不是 Map 的话            // 则须要依据 resultType 来创立 MetaClass ,而后获取 property 对应的 class 信息            MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());            // 失去了 MetaClass 后,调用 getGetterType 办法            // 底层是调用 Reflector#getGetterType 来获取 resultType 的 property 的 class 信息            javaType = metaResultType.getGetterType(property);        }    }    if (javaType == null) {        javaType = Object.class;    }    return javaType;}

3 解析 resultMap 标签

在解析 <resultMap> 标签的过程中,会解析它的 idtype 属性值,以确定 resultMap 的 id 跟 java 类型。
而后解析它的子标签,如: <constructor> , <discriminator> , <id> , <result> 等子标签,而后将这些子标签转换成 ResultMapping 对象来示意。
接着创立 ResultMapResolver 对象,并把下面失去的 idtypeResultMapping 对象,传入到 ResultMapResolver 对象中去。
最初调用 ResultMapResolver#resolve() 办法,创立出 ResultMap 对象,并把该对象存入到 ConfigurationresultMaps 中去。

3.1 解析 resultMap 外围源码

// org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElementprivate ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());    /*        解析 resultMap 的 type 属性,会顺次依照以下程序查找 type 属性        type->ofType->resultType->javaType    */    String type = resultMapNode.getStringAttribute("type",        resultMapNode.getStringAttribute("ofType",            resultMapNode.getStringAttribute("resultType",                resultMapNode.getStringAttribute("javaType"))));    Class<?> typeClass = resolveClass(type);    if (typeClass == null) {        // 如果没有找到 resultMap 的类型,则尝试从参数 enclosingType 中获取        typeClass = inheritEnclosingType(resultMapNode, enclosingType);    }    Discriminator discriminator = null;    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);    List<XNode> resultChildren = resultMapNode.getChildren();    /*        解析 resultMap 下的各个子标签,每个子标签都会生成一个 ResultMapping 对象        这些 resultMapping 对象会增加到 resultMappings 汇合中去        这里波及到 <id> , <result> , <association> , <collection> , <discriminator> 标签    */    for (XNode resultChild : resultChildren) {        if ("constructor".equals(resultChild.getName())) {            // 解析 <constructor> 标签            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);            }            // 解析 <id> 、 <result> 标签            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));        }    }    // 获取 <resultMap> 标签的 id 属性,默认值会拼装所有父标签的 id、value、property 属性值    String id = resultMapNode.getStringAttribute("id",resultMapNode.getValueBasedIdentifier());    // 获取 resultMap 的 <extends> 和 <autoMapping> 属性    String extend = resultMapNode.getStringAttribute("extends");    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");    // 创立 ResultMapResolver 对象,ResultMapResolver 会依据下面解析到的 ResultMappings 汇合以及 <resultMap> 标签的属性    ResultMapResolver resultMapResolver =         new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);    try {        // 结构 resultMap 对象,并将其增加到 Configuration.resultMaps 汇合中去,而后返回 resultMap 对象        return resultMapResolver.resolve();    } catch (IncompleteElementException e) {        configuration.addIncompleteResultMap(resultMapResolver);        throw e;    }}

3.2 buildResultMappingFromContext 办法

该办法是解析 <resultMap> 下的 <constructor><id><result> 标签的次要入口。

源码:

// org.apache.ibatis.builder.xml.XMLMapperBuilder#buildResultMappingFromContextprivate ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {    String property;    // 如果是 <construct> 标签,则取 name 属性,否则取 property 属性    if (flags.contains(ResultFlag.CONSTRUCTOR)) {        property = context.getStringAttribute("name");    } else {        property = context.getStringAttribute("property");    }    // 获取 column、javaType、jdbcType、select 等一系列属性    String column = context.getStringAttribute("column");    String javaType = context.getStringAttribute("javaType");    String jdbcType = context.getStringAttribute("jdbcType");    String nestedSelect = context.getStringAttribute("select");    // 如果是 <association> 和 <collection> 标签,则进行解析    // 如果 resultMap 属性有值,则间接取 resultMap 的属性值,否则就要去获取 resultMap 的 id(如果标签的 select 属性不为空,那么 resultMap 的 id 就为 null)    // processNestedResultMappings 其实就是递归调用 org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElement() 办法    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"));    // 依据下面失去的属性值,获取标签对应的 javaTypeClass , typeHandler , jdbcType 的类型    Class<?> javaTypeClass = resolveClass(javaType);    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);    // 创立 ResultMapping 对象 ,源码在上面    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect,         nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);}// org.apache.ibatis.builder.MapperBuilderAssistant#buildResultMappingpublic ResultMapping buildResultMapping(    Class<?> resultType,    String property,    String column,    Class<?> javaType,    JdbcType jdbcType,    String nestedSelect,    String nestedResultMap,    String notNullColumn,    String columnPrefix,    Class<? extends TypeHandler<?>> typeHandler,    List<ResultFlag> flags,    String resultSet,    String foreignColumn,    boolean lazy) {    // 如果 javaType 为 null 的话 ,间接拿 resultMap( resultType 参数) 的 class 下的属性/setter 办法( property 参数)的 class    // 如果 javaType 不为 null 的话,则间接是拿 javaType 的 class    Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);    // 获取 typeHandler    TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);    List<ResultMapping> composites;    if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {        composites = Collections.emptyList();    } else {        composites = parseCompositeColumnName(column);    }    return new ResultMapping.Builder(configuration, property, column, javaTypeClass)        .jdbcType(jdbcType)        .nestedQueryId(applyCurrentNamespace(nestedSelect, true))        .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))        .resultSet(resultSet)        .typeHandler(typeHandlerInstance)        .flags(flags == null ? new ArrayList<>() : flags)        .composites(composites)        .notNullColumns(parseMultipleColumnNames(notNullColumn))        .columnPrefix(columnPrefix)        .foreignColumn(foreignColumn)        .lazy(lazy)        .build();}

3.3 ResultMapResolver#resolve() 办法

失去了 ResultMapping 对象的列表后,则须要将 resultMap 的 id , type , resultMapping 等属性,通过 ResultMapResolver#resolve() 办法创立 ResultMap 对象进去,再增加到 Configuration.resultMaps 中去。

源码:

// org.apache.ibatis.builder.ResultMapResolver#resolvepublic ResultMap resolve() {    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);}// org.apache.ibatis.builder.MapperBuilderAssistant#addResultMappublic ResultMap addResultMap(    String id,    Class<?> type,    String extend,    Discriminator discriminator,    List<ResultMapping> resultMappings,    Boolean autoMapping) {    // ResultMap 的残缺 id 是 "namespace.id" 的格局 , namespace 就是 mapper 的 namespace     id = applyCurrentNamespace(id, false);    // 获取被继承的 ResultMap 的残缺 id,也就是父 ResultMap 对象的残缺 id    extend = applyCurrentNamespace(extend, true);    // 针对 extend 属性的解决    if (extend != null) {        // 针对 extend 检测 Configuration.resultMaps 汇合中是否存在被继承的 ResultMap 对象        if (!configuration.hasResultMap(extend)) {            throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");        }        // 获取须要被继承的 resultMap 对象,也就是父 ResultMap 对象        ResultMap resultMap = configuration.getResultMap(extend);        // 获取父对 ResultMap 对象中记录的 ResultMapping 汇合        List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());        // 删除须要笼罩的 ResultMapping 汇合        extendedResultMappings.removeAll(resultMappings);        /*        *  如果以后 <resultMap> 标签中定义了 <constructor> 标签,        *  则不须要应用父 ResultMap 中记录的相应 <constructor> 标签,这里会将其对应的 resultMapping 对象删除        */        boolean declaresConstructor = false;        for (ResultMapping resultMapping : resultMappings) {            if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {                declaresConstructor = true;                break;            }        }        if (declaresConstructor) {            extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));        }        // 增加须要被继承的 resultMapping 对象记录到 resultMappings 汇合中        resultMappings.addAll(extendedResultMappings);    }    // 创立 resultMap 对象,并增加到 Configuration.resultMaps 汇合中    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)        .discriminator(discriminator)        .build();    configuration.addResultMap(resultMap);    return resultMap;}

4 解析 sql 标签

解析 <sql> 标签的流程则比较简单,次要是扫描所有 <sql> 的标签,并把它们保留到 XMLMapperBuildersqlFragments Map 中去。其中 sqlFragments 的 key 为 <sql> 标签的 id 属性,value 为 <sql> 标签对应的 XNode 对象。
对应源码在 org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlElement(java.util.List<org.apache.ibatis.parsing.XNode>) 办法上。

5 解析 select , insert , update , delete 的标签

工作流程如下:

  1. 依据标签的名字来决定 SQL 的类型,是 select , insert 还是其余 SQL 语句,并用 SqlCommandType 来示意。
  2. 如果标签内蕴含 <include> 的子标签,则将 <include> 的内容转换成 SQL 片段。
  3. 获取标签的 parameterType ,确定标签的参数类型。
  4. 解决 <selectKey> 标签,并解析 <selectKey> 标签来失去 KeyGenerator 对象。
  5. 通过 LanguageDriver.createSqlSource() 办法来创立 SqlSource 对象,这个过程会解析标签内的 <if> , <where> 等子标签。
  6. 获取SQL标签中配置的 resultSets、keyProperty、keyColumn 等属性,以及后面解析 <selectKey> 标签失去的 KeyGenerator 对象等,这些信息将会填充到 MappedStatement 对象中。
  7. 根据上述属性信息创立 MappedStatement 对象,并增加到 Configuration.mappedStatements 汇合中保留

源码如下:

// org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNodepublic void parseStatementNode() {    // 获取 id 跟 databaseId    String id = context.getStringAttribute("id");    String databaseId = context.getStringAttribute("databaseId");    /*        1. 如果 databaseId 跟以后的不匹配,则不加载该 SQL 标签        2. 如果存在雷同 id 且 databaseId 不为空的 SQL 标签,则不再加载该 SQL 标签    */    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {        return;    }    // 依据 SQL 标签名来决定其 SqlCommandType, SqlCommandType 用来判断是以后的 SQL 标签是 select , insert , update , delete 还是 flush    String nodeName = context.getNode().getNodeName();    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);    // 在解析 SQL 标签前,先解决 <include> 标签    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);    // 将 <include> 标签的内容转换成 SQL 片段    includeParser.applyIncludes(context.getNode());    // 获取 SQL 标签的 parameterType 和 lang 属性    String parameterType = context.getStringAttribute("parameterType");    Class<?> parameterTypeClass = resolveClass(parameterType);    String lang = context.getStringAttribute("lang");    LanguageDriver langDriver = getLanguageDriver(lang);    // 解决 <selectKey> 标签    processSelectKeyNodes(id, parameterTypeClass, langDriver);    // 获取其余属性,而后持续解析这些属性    // 获取/创立主键 id 生成器,如果是标签上的 useGeneratedKeys 的属性为 true    // 或者是 useGeneratedKeys 没有值,mybatis-config.xml 上设置了 userGeneratedKey ,并且是 Insert 语句    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;    }    // 通过 LanguageDriver.createSqlSource() 办法来创立 SqlSource 对象    // 这里会解析 if , where , case 等子标签    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);    /*        获取SQL标签中配置的resultSets、keyProperty、keyColumn等属性,以及后面解析<selectKey>标签失去的KeyGenerator对象等,        这些信息将会填充到MappedStatement对象中    */    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");    // 根据上述属性信息创立MappedStatement对象,并增加到Configuration.mappedStatements汇合中保留    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,        resultSetTypeEnum, flushCache, useCache, resultOrdered,        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}

5.1 获取标签对应的 SQL 类型

MyBatis 依据 SqlCommandType 枚举类来决定该 SQL 的类型,该 SqlCommandType 会最初在 MapperBuilderAssistant#addMappedStatement() 办法中,作为其中一个参数增加进去。
源码如下:

// org.apache.ibatis.mapping.SqlCommandTypepublic enum SqlCommandType {  UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH}

5.2 解析 include 标签

MyBatis 通过创立 XMLIncludeTransformer 对象,并通过其 applyIncludes(XNode) 办法,来将 <include> 标签援用的 <sql> 标签,转换成 SQL 片段。
源码如下

// org.apache.ibatis.builder.xml.XMLIncludeTransformer#applyIncludes(org.w3c.dom.Node)public void applyIncludes(Node source) {    Properties variablesContext = new Properties();    Properties configurationVariables = configuration.getVariables();    Optional.ofNullable(configurationVariables).ifPresent(variablesContext::putAll);    applyIncludes(source, variablesContext, false);}// org.apache.ibatis.builder.xml.XMLIncludeTransformer#applyIncludes(org.w3c.dom.Node, java.util.Properties, boolean)private void applyIncludes(Node source, final Properties variablesContext, boolean included) {    if ("include".equals(source.getNodeName())) {        // 通过 <include> 标签的 refid 字段,找到对应的 <sql> 标签,失去 Node 对象        Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);        /*            解析 <include> 标签下的 <property> 标签,将失去的键值增加到 variablesContext 汇合(Properties 类型)            并造成新的 Properties 对象返回,用于替换占位符        */        Properties toIncludeContext = getVariablesContext(source, variablesContext);        /*            递归执行 applyIncludes()办法,因为在 <sql> 标签的定义中可能会应用 <include> 援用其余 SQL 片段,            在 applyIncludes()办法递归的过程中,如果遇到“${}”占位符,则应用 variablesContext 汇合中的键值对进行替换;            最初,将 <include> 标签替换成 <sql> 标签的内容。        */        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));    }}

5.3 获取 SqlSource 对象

MyBatis 通过调用 getLanguageDriver() 办法,以获取 LanguageDriver 对象,并调用其 createSqlSource() 办法,以获取 SqlSource 对象。
LanguageDriver 的默认实现类是 XMLLanguageDriver ,它的 createSqlSource() 会解析 <if> , <where> , <trim> 等其余标签。
具体逻辑在 org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode() 办法中。
XMLScriptBuilder 外部保护了 NodeHandler 接口以及其实现类,NodeHandler 负责解析动静 SQL 内的标签,生成相应的 SqlNode 对象。
<trim> 标签为例:

private interface NodeHandler {    // 将 nodeToHandle 参数,转化成 SqlNode 对象,并增加到 targetContents 列表中去    void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);}private class TrimHandler implements NodeHandler {    public TrimHandler() {    }    @Override    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {        // 通过 org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags 办法失去 MixedSqlNode         MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);        // 解析 trim 标签的 prefix , prefixOverrides , suffix , suffixOverrides 属性        String prefix = nodeToHandle.getStringAttribute("prefix");        String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");        String suffix = nodeToHandle.getStringAttribute("suffix");        String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");        // 将这些属性作为构造方法的参数,创立出 TrimSqlNode 对象进去        TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);        targetContents.add(trim);    }}

集成了 Spring 模块的状况

在集成了 MyBatis/Spring 或者是 MyBatis/Spring-Boot-Starter 下,则是应用 SqlSessionFactoryBean 来实现 SqlSessionFactory 对象的创立。
次要办法在 org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory 办法下:

public class SqlSessionFactoryBean    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {        // 因为 SqlSessionFactoryBean 继承了 InitializingBean ,实现了该办法    // 故在 Spring 初始化该 Bean 时,会调用该办法,而后调用 buildSqlSessionFactory() 办法,来进行 MyBatis 的初始化和启动流程,并获取 SqlSessionFactory 对象    @Override    public void afterPropertiesSet() throws Exception {        notNull(dataSource, "Property 'dataSource' is required");        notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");        state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),            "Property 'configuration' and 'configLocation' can not specified with together");        this.sqlSessionFactory = buildSqlSessionFactory();    }    // 创立 SqlSessionFactory     // 与 XMLConfigBuilder#parseConfiguration 办法相似    // 往 Configuration 对象设置全局配置信息    // 最初再调用 SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration) 办法实现 MyBatis 的启动    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {                final Configuration targetConfiguration;        XMLConfigBuilder xmlConfigBuilder = null;        if (this.configuration != null) {            targetConfiguration = this.configuration;        if (targetConfiguration.getVariables() == null) {            targetConfiguration.setVariables(this.configurationProperties);        } else if (this.configurationProperties != null) {            targetConfiguration.getVariables().putAll(this.configurationProperties);        }        } else if (this.configLocation != null) {            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);            targetConfiguration = xmlConfigBuilder.getConfiguration();        } else {            LOGGER.debug(                () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");            targetConfiguration = new Configuration();            Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);        }        Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);        Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);        Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);        if (hasLength(this.typeAliasesPackage)) {            scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()                .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())                .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);        }        if (!isEmpty(this.typeAliases)) {            Stream.of(this.typeAliases).forEach(typeAlias -> {                targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);                LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");            });        }        if (!isEmpty(this.plugins)) {            Stream.of(this.plugins).forEach(plugin -> {                targetConfiguration.addInterceptor(plugin);                LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");            });        }        if (hasLength(this.typeHandlersPackage)) {            scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())                .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))                .forEach(targetConfiguration.getTypeHandlerRegistry()::register);        }        if (!isEmpty(this.typeHandlers)) {            Stream.of(this.typeHandlers).forEach(typeHandler -> {                targetConfiguration.getTypeHandlerRegistry().register(typeHandler);                LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");            });        }        targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);        if (!isEmpty(this.scriptingLanguageDrivers)) {            Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {                targetConfiguration.getLanguageRegistry().register(languageDriver);                LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");            });        }        Optional.ofNullable(this.defaultScriptingLanguageDriver)            .ifPresent(targetConfiguration::setDefaultScriptingLanguage);        if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls            try {                targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));            } catch (SQLException e) {                throw new IOException("Failed getting a databaseId", e);            }        }        Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);        if (xmlConfigBuilder != null) {            try {                xmlConfigBuilder.parse();                LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");            } catch (Exception ex) {                throw new IOException("Failed to parse config resource: " + this.configLocation, ex);            } finally {                ErrorContext.instance().reset();            }        }        targetConfiguration.setEnvironment(new Environment(this.environment,            this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,            this.dataSource)        );        if (this.mapperLocations != null) {            if (this.mapperLocations.length == 0) {                LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");            } else {                for (Resource mapperLocation : this.mapperLocations) {                    if (mapperLocation == null) {                        continue;                    }                    try {                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),                            targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());                        xmlMapperBuilder.parse();                    } catch (Exception e) {                        throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);                    } finally {                        ErrorContext.instance().reset();                    }                    LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");                }            }        } else {            LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");        }        return this.sqlSessionFactoryBuilder.build(targetConfiguration);    }    @Override    public SqlSessionFactory getObject() throws Exception {        if (this.sqlSessionFactory == null) {            afterPropertiesSet();        }        return this.sqlSessionFactory;    }}

集成 MyBatis/Spring-Boot-Starter 模块

MyBatis/Spring-Boot-Starter 通过 MybatisAutoConfigurationsqlSessionFactory() 办法,来主动创立 SqlSessionFactory 对象。
该办法实际上就是创立一个 SqlSessionFactoryBean 的对象,并调用其 getObject() 办法,来实现 MyBatis 的启动流程。

源码如下:

@org.springframework.context.annotation.Configuration(proxyBeanMethods = false)@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})@ConditionalOnSingleCandidate(DataSource.class)@EnableConfigurationProperties(MybatisProperties.class)@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})public class MybatisAutoConfiguration implements InitializingBean {    private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);    private final MybatisProperties properties;    private final Interceptor[] interceptors;    private final TypeHandler[] typeHandlers;    private final LanguageDriver[] languageDrivers;    private final ResourceLoader resourceLoader;    private final DatabaseIdProvider databaseIdProvider;    private final List<ConfigurationCustomizer> configurationCustomizers;    private final List<SqlSessionFactoryBeanCustomizer> sqlSessionFactoryBeanCustomizers;    public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,                                    ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,                                    ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,                                    ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,                                    ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) {        this.properties = properties;        this.interceptors = interceptorsProvider.getIfAvailable();        this.typeHandlers = typeHandlersProvider.getIfAvailable();        this.languageDrivers = languageDriversProvider.getIfAvailable();        this.resourceLoader = resourceLoader;        this.databaseIdProvider = databaseIdProvider.getIfAvailable();        this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();        this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();    }    @Bean    @ConditionalOnMissingBean    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();        factory.setDataSource(dataSource);        if (properties.getConfiguration() == null || properties.getConfiguration().getVfsImpl() == null) {            factory.setVfs(SpringBootVFS.class);        }        if (StringUtils.hasText(this.properties.getConfigLocation())) {            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));        }        applyConfiguration(factory);        if (this.properties.getConfigurationProperties() != null) {            factory.setConfigurationProperties(this.properties.getConfigurationProperties());        }        if (!ObjectUtils.isEmpty(this.interceptors)) {            factory.setPlugins(this.interceptors);        }        if (this.databaseIdProvider != null) {            factory.setDatabaseIdProvider(this.databaseIdProvider);        }        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());        }        if (this.properties.getTypeAliasesSuperType() != null) {            factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());        }        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());        }        if (!ObjectUtils.isEmpty(this.typeHandlers)) {            factory.setTypeHandlers(this.typeHandlers);        }        // 如果配置文件 application.yaml 的 mybatis.mapperLocations 信息不为空,那么就会设置 mapper.xml 的加载门路        Resource[] mapperLocations = this.properties.resolveMapperLocations();        if (!ObjectUtils.isEmpty(mapperLocations)) {            factory.setMapperLocations(mapperLocations);        }        Set<String> factoryPropertyNames = Stream                .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)                .collect(Collectors.toSet());        Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();        if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {            // Need to mybatis-spring 2.0.2+            factory.setScriptingLanguageDrivers(this.languageDrivers);            if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {                defaultLanguageDriver = this.languageDrivers[0].getClass();            }        }        if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {            // Need to mybatis-spring 2.0.2+            factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);        }        applySqlSessionFactoryBeanCustomizers(factory);        // 这里实际上就是调用 SqlSessionFactoryBean 的 buildSqlSessionFactory() 办法        return factory.getObject();    }}