Mybatis 启动流程

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。

快速导航:

Mybatis 配置方式原生XML配置原生编程式配置基于Spring容器配置基于Spring Boot配置Mybatis 初始化流程SqlSessionFactoryBuilderXMLConfigBuilder1. 构建Configuration对象2. 调用父类构造器2.1 parseExpression2.2 xxxValueOf2.3 resolveXxxxxx2.4 createInstance3. 初始化其他参数4. 解析xml文件4.1 解析properties标签4.2 解析setting标签4.3 设置vfs4.4 解析 typeAliases 标签4.1.1 别名注册到TypeAliasRegistry4.5 解析 plugins 标签4.6 解析 objectFactory 标签4.7 解析 objectWrapperFactory 标签4.8 解析 reflectorFactory 标签4.9 通过 settings 的属性修改 configuration的配置4.10 解析 environments 标签4.11 解析 databaseIdProvider 标签4.11 解析 typeHandlers 标签4.12 解析 mappers 标签5. 构建SqlSessionFactory

Mybatis 配置方式

Mybatis的启动方式有几种,包括原生的XML配置、原生的编程式配置、基于Spring容器的配置方式和基于Spring Boot的配置方法。

原生XML配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

上面的示例省略了很多配置,只给出最关键的部分

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但是也可以使用任意的输入流(InputStream)实例,包括字符串形式的文件路径或者 file:// 的 URL 形式的文件路径来配置。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,可使从 classpath 或其他位置加载资源文件更加容易。

原生编程式配置

// 创建数据源
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
// 创建事务工厂
TransactionFactory transactionFactory = new JdbcTransactionFactory();
// 创建运行环境
Environment environment = new Environment("development", transactionFactory, dataSource);
// 构建 Configuration 实例
Configuration configuration = new Configuration(environment);
// 配置映射
configuration.addMapper(BlogMapper.class);
// 构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

基于Spring容器配置

基于Spring容器的配置方式也有XML和编程式的方式,这里以XML的配置方式来说明。其实两者都是没有区别的,本质上都是通过SqlSessionFactoryBean来创建SqlSessionFactoryBuilder,然后再以SqlSessionFactoryBuilder来构建SqlSessionFactory

<!-- 省略部分bean定义如dataSource -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="mapperLocations" value="classpath:org/mybatis/spring/sample/mapper/*.xml" />
</bean>

SqlSessionFactoryBean ,实现 FactoryBeanInitializingBeanApplicationListener 接口,负责创建 SqlSessionFactory 对象。

由于SqlSessionFactoryBean 实现了InitializingBean接口,在bean初始化的时候会调用其afterPropertiesSet回调,SqlSessionFactoryBean 通过该机制来创建SqlSessionFactory

public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");

    this.sqlSessionFactory = buildSqlSessionFactory();
}

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configLocation != null) {
        // 如果是通过xml配置的就创建XMLConfigBuilder来解析xml文件并获取Configuration对象
        xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), nullthis.configurationProperties);
        configuration = xmlConfigBuilder.getConfiguration();
    } else {
        // 构建默认的Configuration并设置Properties
        configuration = new Configuration();
        configuration.setVariables(this.configurationProperties);
    }

    // 如果有配置对应变量的话,就设置到configuration的属性中
    // 现在不用太关注这些属性,我们只看启动流程,后面会再分析
    if (this.objectFactory != null) {
        configuration.setObjectFactory(this.objectFactory);
    }

    if (this.objectWrapperFactory != null) {
        configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }

    if (hasLength(this.typeAliasesPackage)) {
        String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packageToScan : typeAliasPackageArray) {
            configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                    typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Scanned package: '" + packageToScan + "' for aliases");
            }
        }
    }

    if (!isEmpty(this.typeAliases)) {
        for (Class<?> typeAlias : this.typeAliases) {
            configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        }
    }

    if (!isEmpty(this.plugins)) {
        for (Interceptor plugin : this.plugins) {
            configuration.addInterceptor(plugin);
        }
    }

    if (hasLength(this.typeHandlersPackage)) {
        String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packageToScan : typeHandlersPackageArray) {
            configuration.getTypeHandlerRegistry().register(packageToScan);
        }
    }

    if (!isEmpty(this.typeHandlers)) {
        for (TypeHandler<?> typeHandler : this.typeHandlers) {
            configuration.getTypeHandlerRegistry().register(typeHandler);
        }
    }

    if (xmlConfigBuilder != null) {
        try {
            xmlConfigBuilder.parse();
        } catch (Exception ex) {
            ...
        } finally {
            ErrorContext.instance().reset();
        }
    }

    if (this.transactionFactory == null) {
        this.transactionFactory = new SpringManagedTransactionFactory();
    }

    Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
    configuration.setEnvironment(environment);

    if (this.databaseIdProvider != null) {
        try {
            configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
        } catch (SQLException e) {
            ...
        }
    }

    if (!isEmpty(this.mapperLocations)) {
        for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
                continue;
            }

            try {
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                        configuration, mapperLocation.toString(), configuration.getSqlFragments());
                xmlMapperBuilder.parse();
            } catch (Exception e) {
                ...
            } finally {
                ErrorContext.instance().reset();
            }

        }
    }
    // 重点! 调用sqlSessionFactoryBuilder进行构建
    return this.sqlSessionFactoryBuilder.build(configuration);
}

// 如果我们没有指定sqlSessionFactoryBuilder的话,默认使用Mybatis的SqlSessionFactoryBuilder
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

上面一长串,重点其实在最后一部分~

由于SqlSessionFactoryBean是工厂bean,所以在获取真实bean的时候是调用其getObject方法的:

public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
        // 保证 SqlSessionFactory 对象的初始化
        afterPropertiesSet();
    }

    return this.sqlSessionFactory;
}

基于Spring Boot配置

这里已官方提供的 mybatis-spring-boot-starter 来分析。

mybatis-spring-boot-starter里面其实就只有一个pom文件。其中,关键的依赖为

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>

mybatis-spring-boot-autoconfigure模块中提供了spring.factories,以便在Spring Boot在启动时加载并实例化

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

本篇只关注MybatisAutoConfigurationMybatisAutoConfiguration通过@Configuration声明了自己是一个配置类,并通过@Bean来提供sqlSessionFactory

@Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
    // 省略无关代码
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        // 创建SqlSessionFactoryBean对象
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        // 设置相关属性
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }
        factory.setConfiguration(properties.getConfiguration());
        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 (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }
        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
            factory.setMapperLocations(this.properties.resolveMapperLocations());
        }
        // 获取sqlSessionFactory
        return factory.getObject();
    }
    // 省略无关代码
}

通过上面的源码,我们知道核心跟基于Spring容器的配置方式是一样的,都是通过SqlSessionFactoryBean来构建mybatis。

Mybatis 初始化流程

通过上面的分析,我们知道,无论是哪种配置方式,最终都是通过SqlSessionFactoryBuilder来构建我们的SqlSessionFactory,那么我们就从SqlSessionFactoryBuilder入手分析:

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder重载了很多build方法,但是核心都是一样的,通过XMLConfigBuilder来解析xml获得Configuration,然后使用public SqlSessionFactory build(Configuration config)来构建SqlSessionFactory

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
        return build(parser.parse());
    } catch (Exception e) {
        // 省略,抛出构建失败的异常
    } finally {
        // 省略,关闭reader IO
    }
}

public SqlSessionFactory build(Configuration config) {
    // Mybatis默认提供的SqlSessionFactory实现
    return new DefaultSqlSessionFactory(config);
}

对于非xml配置,是手动把相关属性注册到SqlSessionFactoryBean中,SqlSessionFactoryBean用这些属性来构建Configuration,最终还是通过上面的build方法来构建。

那么我们就跟着他的流程,从解析xml配置开始~

XMLConfigBuilder

XMLConfigBuilder有多个构造方法,最终都会调用到下面这个构造器上:

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    // 创建 Configuration 对象
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    // 设置 Configuration 的 variables 属性
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}
1. 构建Configuration对象

Mybatis整个核心都是基于Configuration类,在构建Configuration时初始化了很多相关的注册表和默认配置:

public class Configuration {
    // =================仅展示部分属性=================
    // 配置属性
    protected Properties variables = new Properties();
    // 发射工厂
    protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    // 对象工厂
    protected ObjectFactory objectFactory = new DefaultObjectFactory();
    // 对象包装工厂的
    protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

    // mapper注册表
    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    // 拦截器注册表
    protected final InterceptorChain interceptorChain = new InterceptorChain();
    // 类型转换注册表(默认注册了很多java和数据库字段类型的映射)
    protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
    // 别名注册表
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
    // LanguageDriver注册表
    protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

    // mappedStatements缓存
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
    // 一级缓存
    protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
    // resultMaps缓存
    protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
    // 参数缓存
    protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
    // id生成器缓存
    protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

    // 构造器
    public Configuration() {
        // 注册别名
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

        typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
        typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

        typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
        typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
        typeAliasRegistry.registerAlias("LRU", LruCache.class);
        typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

        typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

        typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

        typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
        typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
        typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
        typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
        typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
        typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

        typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
        typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        languageRegistry.register(RawLanguageDriver.class);
    }
}

各种注册表中除了拦截器使用ArrayList作为底层数据存储外,其他都是使用Map数据结构。其中以下几个使用了Mybatis继承HashMap的子类StrictMap

  • mappedStatements
  • Key为mapper的namespace + “.” + (<select>|<insert>|<update>|<delete>标签中的id属性)
  • Value为MappedStatement对象
  • caches
  • Key为mapper的namespace
  • Value为Cache的实例(一级缓存)
  • resultMaps
  • Key为当前mapper的namespace+”.”+<resultMap>标签中的id属性
  • Value为ResultMap对象
  • parameterMaps
  • Key为当前mapper的namespace+”.”+<parameterMap>标签中的id属性
  • Value为ParameterMap对象
  • keyGenerators
  • Key为当前mapper的namespace+”.”+<insert>标签中的id属性+”!selectKey”
    • 仅对insert标签有效,且配置了`useGeneratedKeys=”true”`的就使用`Jdbc3KeyGenerator`,如果`useGeneratedKeys`为false则使用`NoKeyGenerator`(`KeyGenerator`的空实现)
  • Value为KeyGenerator的实例
  • sqlFragments
  • Key为当前mapper的namespace+”.”+<sql>标签中的id属性
  • Value为sql这个XNode本身

StrictMapConfiguration的内部类,继承于HashMap,重写了getput方法,提供更加严格的校验。

protected static class StrictMap<Vextends HashMap<StringV{

    private static final long serialVersionUID = -4950446264854982944L;
    private final String name;

    // 省略了构造器,只展示get / put 相关逻辑

    @SuppressWarnings("unchecked")
    public V put(String key, V value) {
        if (containsKey(key)) {
            // 抛出非法参数异常,key已存在
        }
        if (key.contains(".")) {
            final String shortKey = getShortName(key);
            if (super.get(shortKey) == null) {
                super.put(shortKey, value);
            } else {
                super.put(shortKey, (V) new Ambiguity(shortKey));
            }
        }
        return super.put(key, value);
    }

    public V get(Object key) {
        V value = super.get(key);
        if (value == null) {
            // 抛出非法参数异常,value已存在
        }
        if (value instanceof Ambiguity) {
            // 抛出key获取到的value是歧义的(即)
        }
        return value;
    }

    // 去掉前缀(namespace)
    private String getShortName(String key) {
        final String[] keyParts = key.split("\\.");
        return keyParts[keyParts.length - 1];
    }

    protected static class Ambiguity {
        final private String subject;

        public Ambiguity(String subject) {
            this.subject = subject;
        }

        public String getSubject() {
            return subject;
        }
    }
}

目前我们已经得到了一个默认的Configuration的对象了~

2. 调用父类构造器
// BaseBuilder.java

public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}

BaseBuilderXMLConfigBuilder的抽象父类,为子类提供通用的工具类。

2.1 parseExpression

创建正则表达式

protected Pattern parseExpression(String regex, String defaultValue) {
    return Pattern.compile(regex == null ? defaultValue : regex);
}
2.2 xxxValueOf

将字符串转换成对应的数据类型的值

protected Boolean booleanValueOf(String value, Boolean defaultValue) {
    return value == null ? defaultValue : Boolean.valueOf(value);
}
// 省略其他类似方法
2.3 resolveXxxxxx

解析对应的参数/模块

protected JdbcType resolveJdbcType(String alias) {
    if (alias == null) {
        return null;
    }
    try {
        return JdbcType.valueOf(alias);
    } catch (IllegalArgumentException e) {
        throw new BuilderException("Error resolving JdbcType. Cause: " + e, e);
    }
}
// 省略其他类似方法
2.4 createInstance

解析别名对应的class并实例化

protected Object createInstance(String alias) {
    Class<?> clazz = resolveClass(alias);
    if (clazz == null) {
        return null;
    }
    try {
        return resolveClass(alias).newInstance();
    } catch (Exception e) {
        throw new BuilderException("Error creating instance. Cause: " + e, e);
    }
}
3. 初始化其他参数
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    // 创建 Configuration 对象
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    // 初始化其他参数
    // 设置 Configuration 的 variables 属性
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}

现在XMLConfigBuilder也构建完成了,在SqlSessionFactoryBuilder提供的build方法中接下来的步骤是就是解析

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
        return build(parser.parse());
    } catch (Exception e) {
        // 省略,抛出构建失败的异常
    } finally {
        // 省略,关闭reader IO
    }
}
4. 解析xml文件
public Configuration parse() {
    if (parsed) {
        // 如果已经解析过,就抛出异常
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 重点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

通过XPath把xml配置文件转换为XNode对象

获取文档的configuration节点(根节点),然后调用parseConfiguration进行解析。

private void parseConfiguration(XNode root) {
    try {
        // 4.1 解析properties标签
        propertiesElement(root.evalNode("properties"));
        // 4.2 解析setting标签
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        // 4.3 设置vfs
        loadCustomVfs(settings);
        // 4.4 解析 typeAliases 标签
        typeAliasesElement(root.evalNode("typeAliases"));
        // 4.5 解析 plugins 标签
        pluginElement(root.evalNode("plugins"));
        // 4.6 解析 objectFactory 标签
        objectFactoryElement(root.evalNode("objectFactory"));
        // 4.7 解析 objectWrapperFactory 标签
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        // 4.8 解析 reflectorFactory 标签
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        // 4.9 通过 settings 的属性修改 configuration的配置
        settingsElement(settings);
        // 4.10 解析 environments 标签
        environmentsElement(root.evalNode("environments"));
        // 4.11 解析 databaseIdProvider 标签
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // 4.11 解析 typeHandlers 标签
        typeHandlerElement(root.evalNode("typeHandlers"));
        // 4.12 解析 mappers 标签
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}
4.1 解析properties标签
propertiesElement(root.evalNode("properties"));

properties标签示例如下:

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="root"/>
  <property name="password" value="123456"/>
</properties>
private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        // property 标签
        Properties defaults = context.getChildrenAsProperties();
        // propertie标签的属性,resource通过指定配置文件路径加载配置
        String resource = context.getStringAttribute("resource");
        // propertie标签的属性url通过提供网络访问配置文件
        String url = context.getStringAttribute("url");
        if (resource != null && url != null) {
            // 省略,同时指定则抛出异常
        }
        // 通过自带的Resources读取并加入到配置中
        if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        // 添加configuration中默认的配置(目前为空)
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        // 设置到parser中,方便后续解析替换
        parser.setVariables(defaults);
        // 重新赋值到configuration中
        configuration.setVariables(defaults);
    }
}

关于Resources是如何加载配置文件的,这里不做深入。

4.2 解析setting标签
// 默认的反射工厂,用于构建指定Class的反射工具类
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

private Properties settingsAsProperties(XNode context) {
    if (context == null) {
        return new Properties();
    }
    // 解析子标签为属性对象
    Properties props = context.getChildrenAsProperties();
    // 检查Configuration是否包含这些配置属性
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
        // 检查setter
        if (!metaConfig.hasSetter(String.valueOf(key))) {
            // setter不存在则抛出异常
        }
    }
    return props;
}

MetaClass是Mybatis封装的类的元数据,通过ReflectorFactory创建Reflector,使用Reflector对类进行一系列的反射操作。而Reflector是Mybatis提供的反射工具类,缓存了get、set方法,属性类型等信息。

一个配置完整的 settings 元素的示例如下(Mybatis默认配置,完整的配置点这里):

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
4.3 设置vfs
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
    // 如果settings标签中配置了指定 VFS 的实现,则添加到configuration的VfsImpl的用户定义VFS缓存中
    String value = props.getProperty("vfsImpl");
    if (value != null) {
        String[] clazzes = value.split(",");
        for (String clazz : clazzes) {
            if (!clazz.isEmpty()) {
                @SuppressWarnings("unchecked")
                Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
                configuration.setVfsImpl(vfsImpl);
            }
        }
    }
}

VFS,提供用于访问应用程序服务器内资源的非常简单的API。

4.4 解析 typeAliases 标签

类型别名是为 Java 类型设置一个短的名字。 它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。例如:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

接下来看看Mybatis是如何解析的:

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                // 如果子标签是 package 就通过 TypeAliasRegistry 进行扫描并注册
                String typeAliasPackage = child.getStringAttribute("name");
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
                // 否则就是 typeAlias  标签,解析 alias 和 type注册到typeAliasRegistry中
                String alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");
                try {
                    Class<?> clazz = Resources.classForName(type);
                    if (alias == null) {
                        // alias 为空时默认生成类名转小写的alias进行注册
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // 抛出异常
                }
            }
        }
    }
}
4.1.1 别名注册到TypeAliasRegistry
// TypeAliasRegistry.java


// 注册表
private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

public void registerAliases(String packageName){
    registerAliases(packageName, Object.class);
}

public void registerAliases(String packageName, Class<?> superType){
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for(Class<?> type : typeSet){
        // 忽略内部类和接口
        if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
            registerAlias(type);
        }
    }
}

public void registerAlias(Class<?> type) {
    String alias = type.getSimpleName();
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
        // 如果被Alias标注则使用标注的值作为key
        alias = aliasAnnotation.value();
    }
    registerAlias(alias, type);
}

public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
        // 抛出异常
    }
    // alias 转为小写
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
        // alias已存在且与之前的value不一致,抛出异常
    }
    TYPE_ALIASES.put(key, value);
}
4.5 解析 plugins 标签

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

  • ParameterHandler (getParameterObject, setParameters)

  • ResultSetHandler (handleResultSets, handleOutputParameters)

  • StatementHandler (prepare, parameterize, batch, update, query)

    通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

拦截器示例:

// ExamplePlugin.java
@Intercepts({@Signature(
        type= Executor.class,
        method = "update",
        args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
    private Properties properties = new Properties();
    public Object intercept(Invocation invocation) throws Throwable {
        // 执行前的处理
        Object returnObject = invocation.proceed();
        // 执行后的处理
        return returnObject;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

xml配置:

<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

上面的插件将会拦截在 Executor 实例中所有的 update 方法调用, 这里的 Executor 是负责执行低层映射语句的内部对象。

源码解析如下:

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 获取拦截器的class,如果配置了alias可以使用alias
            String interceptor = child.getStringAttribute("interceptor");
            // 获取拦截器的参数
            Properties properties = child.getChildrenAsProperties();
            // 实例化拦截器
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            // 设置参数
            interceptorInstance.setProperties(properties);
            // 加入到 configuration 的拦截器链InterceptorChain中
            configuration.addInterceptor(interceptorInstance);
        }
    }
}
4.6 解析 objectFactory 标签

MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。比如:

// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
    public Object create(Class type) {
        return super.create(type);
    }
    public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
        return super.create(type, constructorArgTypes, constructorArgs);
    }
    public void setProperties(Properties properties) {
        super.setProperties(properties);
    }
    public <T> boolean isCollection(Class<T> type) {
        return Collection.class.isAssignableFrom(type);
    }}
}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

ObjectFactory 接口很简单,它包含两个创建用的方法,一个是处理默认构造方法的,另外一个是处理带参数的构造方法的。 最后,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。

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).newInstance();
        // 设置工厂的参数
        factory.setProperties(properties);
        // 设置到 configuration 中
        configuration.setObjectFactory(factory);
    }
}
4.7 解析 objectWrapperFactory 标签

该配置已经废弃了,跳过

4.8 解析 reflectorFactory 标签
private void reflectorFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
        configuration.setReflectorFactory(factory);
    }
}

这个标签也在Mybatis后续的配置文件中移除了

4.9 通过 settings 的属性修改 configuration的配置

只是覆盖一下默认值,跳过也行

private void settingsElement(Properties props) throws Exception {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior""PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior""NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType""SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope""SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull""OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
    configuration.setDefaultEnumTypeHandler(typeHandler);
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    @SuppressWarnings("unchecked")
    Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
4.10 解析 environments 标签

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中 使用相同的 SQL 映射。有许多类似的使用场景。

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:

  • 每个数据库对应一个 SqlSessionFactory 实例

    包含环境的构建方法签名为:

    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

xml配置:

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

注意这里的关键点:

  • 默认使用的环境 ID(比如:default=”development”)。
  • 每个 environment 元素定义的环境 ID(比如:id=”development”)。
  • 事务管理器的配置(比如:type=”JDBC”)。
  • 数据源的配置(比如:type=”POOLED”)。

默认的环境和环境 ID 是自解释的,因此一目了然。 你可以对环境随意命名,但一定要保证默认的环境 ID 要匹配其中一个环境 ID。

源码如下:

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (environment == null) {
            environment = context.getStringAttribute("default");
        }
        for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                        .transactionFactory(txFactory)
                        .dataSource(dataSource);
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}
4.11 解析 databaseIdProvider 标签

基本上很少使用,了解一下就行

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可

<databaseIdProvider type="DB_VENDOR" />

DB_VENDOR 对应的 databaseIdProvider 实现会将 databaseId 设置为 DatabaseMetaData#getDatabaseProductName() 返回的字符串。 由于通常情况下这些字符串都非常长而且相同产品的不同版本会返回不同的值,所以你可能想通过设置属性别名来使其变短,如下:

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

在提供了属性别名时,DB_VENDOR 的 databaseIdProvider 实现会将 databaseId 设置为第一个数据库产品名与属性中的名称相匹配的值,如果没有匹配的属性将会设置为 “null”。 在这个例子中,如果 getDatabaseProductName() 返回“Oracle (DataDirect)”,databaseId 将被设置为“oracle”。

你可以通过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注册来构建自己的 DatabaseIdProvider。

private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
        String type = context.getStringAttribute("type");
        // awful patch to keep backward compatibility
        if ("VENDOR".equals(type)) {
            type = "DB_VENDOR";
        }
        Properties properties = context.getChildrenAsProperties();
        databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
        databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
        String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
        configuration.setDatabaseId(databaseId);
    }
}
4.11 解析 typeHandlers 标签

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。

可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个 JDBC 类型。

// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String{

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter);
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getString(columnName);
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getString(columnIndex);
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getString(columnIndex);
    }
}

然后添加配置:

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

或者

<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>

代码解析如下:

private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                // 通过扫描package注册
                String typeHandlerPackage = child.getStringAttribute("name");
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
                // 通过指定的子标签来注册
                String javaTypeName = child.getStringAttribute("javaType");
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                String handlerTypeName = child.getStringAttribute("handler");
                Class<?> javaTypeClass = resolveClass(javaTypeName);
                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                Class<?> typeHandlerClass = resolveClass(handlerTypeName);
                if (javaTypeClass != null) {
                    if (jdbcType == null) {
                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                    }
                } else {
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }
}

4.11.1 TypeHandlerRegistry

TypeHandlerRegistry注册表与前面TypeAliasRegistry类似,了解一下即可:

// 扫描包注册
public void register(String packageName) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
    Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
    for (Class<?> type : handlerSet) {
        //忽略内部类和接口
        if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
            register(type);
        }
    }
}

public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
        for (Class<?> javaTypeClass : mappedTypes.value()) {
            // 注册为javaTypeClass对应的handler
            register(javaTypeClass, typeHandlerClass);
            mappedTypeFound = true;
        }
    }
    // 没有配置MappedTypes或者没找到MappedTypes配置的类型,使用无参构造器进行实例化并注册到null类型上
    if (!mappedTypeFound) {
        register(getInstance(null, typeHandlerClass));
    }
}

public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
        for (Class<?> handledType : mappedTypes.value()) {
            register(handledType, typeHandler);
            mappedTypeFound = true;
        }
    }
    // @since 3.1.0 - try to auto-discover the mapped type
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
        try {
            TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
            register(typeReference.getRawType(), typeHandler);
            mappedTypeFound = true;
        } catch (Throwable t) {
            // 不处理
        }
    }
    if (!mappedTypeFound) {
        register((Class<T>) null, typeHandler);
    }
}

public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
    register((Type) javaType, typeHandler);
}

private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    // 获取MappedJdbcTypes的注解内容
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
        for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
            // 循环注册该java 类对应的jdbc type
            register(javaType, handledJdbcType, typeHandler);
        }
        if (mappedJdbcTypes.includeNullJdbcType()) {
            register(javaType, null, typeHandler);
        }
    } else {
        register(javaType, null, typeHandler);
    }
}
4.12 解析 mappers 标签

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要定义 SQL 映射语句了。 但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。 Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。例如:

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

这些配置会告诉了 MyBatis 去哪里找映射文件,剩下的细节就应该是每个 SQL 映射文件了。

代码解析:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                // 扫描package下的所有接口,构建MapperAnnotationBuilder进行解析
                configuration.addMappers(mapperPackage);
            } else {
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    // 指定了mapper xml配置,使用XMLMapperBuilder 进行解析
                    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);
                    // 指定了mapper xml配置,使用XMLMapperBuilder 进行解析
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url == null && mapperClass != null) {
                    // 加载指定的class,通过MapperAnnotationBuilder进行解析
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                       // 如果指定了多种方式,则抛出异常
                }
            }
        }
    }
}

XMLMapperBuilderMapperAnnotationBuilder 的分析见Mybatis mapper解析过程

5. 构建SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

就是实例化了默认的DefaultSqlSessionFactory,当Configuration配置好了以后,DefaultSqlSessionFactory只是通过我们配置的参数实例化SqlSession,然后就可以进CRUD了。


6 条评论

QQKeno หวยออนไลน์ · 2024年1月16日 下午6:12

… [Trackback]

[…] Information to that Topic: hugr.cn/2019/12/20/mybatis-启动流程/ […]

แนะนำ บาคาร่า คืออะไร เกมไพ่ยอดนิยมตลอดกาล · 2024年1月16日 下午6:14

… [Trackback]

[…] Read More on that Topic: hugr.cn/2019/12/20/mybatis-启动流程/ […]

fake driving licence · 2024年2月11日 上午9:25

… [Trackback]

[…] Read More on to that Topic: hugr.cn/2019/12/20/mybatis-启动流程/ […]

เครื่องสแกนนิ้ว · 2024年2月15日 上午8:28

… [Trackback]

[…] There you can find 46521 additional Information on that Topic: hugr.cn/2019/12/20/mybatis-启动流程/ […]

ทุบตึก · 2024年2月17日 下午12:42

… [Trackback]

[…] Read More here to that Topic: hugr.cn/2019/12/20/mybatis-启动流程/ […]

gay dating málaga · 2024年3月17日 下午11:21

… [Trackback]

[…] Read More on on that Topic: hugr.cn/2019/12/20/mybatis-启动流程/ […]

评论已关闭。