美文网首页
MyBatis源码阅读【加载】(一)全局配置文件的加载

MyBatis源码阅读【加载】(一)全局配置文件的加载

作者: 云芈山人 | 来源:发表于2021-05-15 23:24 被阅读0次

    前言

    每个基于Mybatis的应用都是以一个SqlSessionFactory实例为核心的。
    SqlSessionFactory创建.png

    生命周期和作用域

    1. 依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。

    2. 非依赖注入框架下使用


      生命周期和作用域.png

    一、全局配置文件

    参考网站:https://mybatis.org/mybatis-3/zh/configuration.html
    MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下

    配置文档顶层结构.png

    1. properties(属性)

    • 这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。
      例如:
    <!--       resource属性:引入类路径下的配置文件
                url属性:引入网络或磁盘上的配置文件     -->
    <properties resource="org/mybatis/example/config.properties">
      <property name="username" value="ceshi"/>
      <property name="password" value="123456"/>
    </properties>
    
    • 设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。
      比如:
    <dataSource type="POOLED">
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
    
    • 如果一个属性在不只一个地方进行了配置,那么,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

    • 从 MyBatis 3.4.2 开始,你可以为占位符指定一个默认值(这个特性默认是关闭的。要启用这个特性,需要添加一个特定的属性来开启这个特性)。
      例如:

    <properties resource="org/mybatis/example/config.properties">
     <!-- 启用默认值特性 -->
      <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> 
    </properties>
    
    <dataSource type="POOLED">
     <!-- 如果属性 'username' 没有被配置,'username' 属性的值将为 'cs_user' -->
      <property name="username" value="${username:cs_user}"/> 
    </dataSource>
    

    如果你在属性名中使用了 ":" 字符(如:db:username),或者在 SQL 映射中使用了 OGNL 表达式的三元运算符(如: ${tableName != null ? tableName : 'global_constants'}),就需要设置特定的属性来修改分隔属性名和默认值的字符。
    例如:

    <properties resource="org/mybatis/example/config.properties">
      <!-- 修改默认值的分隔符 ?:-->
      <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> 
    </properties>
    
    <dataSource type="POOLED">
      <property name="username" value="${db:username?:cs_user}"/>
    </dataSource>
    
    

    2.settings(设置)

    MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
    • 一个配置完整的 settings 元素的示例如下:
    <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>
    
    • 各项设置的含义及默认值


      各项设置的含义、默认值.png

    3.typeAliases(类型别名)

    类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。(简化映射文件中parameterType和ResultType中的POJO类型名称编写,默认支持别名)

    例如:

    <typeAliases>
        <!-- 当这样配置时,A01可以用在任何使用com.business.A01的地方。-->
      <typeAlias alias="A01" type="com.business.A01"/>
      <typeAlias alias="A02" type="com.business.A02"/>
    </typeAliases>
    

    也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

    <typeAliases>
      <!-- 每一个在包 com.business中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 com.business.A01 的别名为 a01; -->
      <package name="com.business"/>
    </typeAliases>
    

    若有注解,则别名为其注解值。比如:

    @Alias("a01")
    public class A01 {
        ...
    }
    
    常见的 Java 类型内建的类型别名,它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

    如:_byte/byte( 别名/映射文件);byte/Byte;hashmap/HashMap等。

    4.typeHandlers(类型处理器)

    MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型(从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API))。下表描述了一些默认的类型处理器。
    一些默认的类型处理器.png

    也可以重写已有的类型处理器或创建自定义的处理器来处理不支持或非标准的类型。实现必须遵循:实现org.apache.ibatis.type.TypeHandler接口,或继承一个和便利的类org.apache.ibatis.type.BaseTypeHandler,并可将它映射到一个JDBC类型。

    <typeHandlers>
      <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
    </typeHandlers>
    

    5.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方法。

    6.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的核心模块。
      通过MyBatis提供的强大机制,使用插件非常简单,只需实现Interceptor接口,并指定想要拦截的方法签名即可。
    // ExamplePlugin.java 该插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。
    @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 {
        // implement pre processing if need
        Object returnObject = invocation.proceed();
        // implement post processing if need
        return returnObject;
      }
      public void setProperties(Properties properties) {
        this.properties = properties;
      }
    }
    
    <!-- mybatis-config.xml -->
    <plugins>
      <plugin interceptor="org.mybatis.example.ExamplePlugin">
        <property name="someProperty" value="100"/>
      </plugin>
    </plugins>
    

    除了用插件来修改 MyBatis 核心行为以外,还可以通过完全覆盖配置类来达到目的。只需继承配置类后覆盖其中的某个方法,再把它传递到 SqlSessionFactoryBuilder.build(myConfig) 方法即可。

    7.environments(环境配置)

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

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

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

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

    为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:

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

    如果忽略了环境参数,那么将会加载默认环境,如下所示:

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

    environments 元素定义了如何配置环境。

    <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。
    transactionManager(事务管理器)

    在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

    • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
    • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>
    

    如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

    这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用 TransactionFactory 接口实现类的全限定名或类型别名代替它们。

    public interface TransactionFactory {
      default void setProperties(Properties props) { // 从 3.5.2 开始,该方法为默认方法
        // 空实现
      }
      Transaction newTransaction(Connection conn);
      Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
    }
    

    在事务管理器实例化后,所有在 XML 中配置的属性将会被传递给 setProperties() 方法。你的实现还需要创建一个 Transaction 接口的实现类,这个接口也很简单:

    public interface Transaction {
      Connection getConnection() throws SQLException;
      void commit() throws SQLException;
      void rollback() throws SQLException;
      void close() throws SQLException;
      Integer getTimeout() throws SQLException;
    }
    

    使用这两个接口,你可以完全自定义 MyBatis 对事务的处理。

    dataSource(数据源)

    dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

    • 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
      有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):


      三种内建的数据源类型.png

    可以通过实现接口 org.apache.ibatis.datasource.DataSourceFactory 来使用第三方数据源实现:

    public interface DataSourceFactory {
      void setProperties(Properties props);
      DataSource getDataSource();
    }
    

    org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父类来构建新的数据源适配器,比如下面这段插入 C3P0 数据源所必需的代码:

    import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    
    public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
    
      public C3P0DataSourceFactory() {
        this.dataSource = new ComboPooledDataSource();
      }
    }
    

    为了令其工作,记得在配置文件中为每个希望 MyBatis 调用的 setter 方法增加对应的属性。 下面是一个可以连接至 PostgreSQL 数据库的例子:

    <dataSource type="org.myproject.C3P0DataSourceFactory">
      <property name="driver" value="org.postgresql.Driver"/>
      <property name="url" value="jdbc:postgresql:mydb"/>
      <property name="username" value="postgres"/>
      <property name="password" value="root"/>
    </dataSource>
    

    8.databaseIdProvider(数据库厂商标识)

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

    <databaseIdProvider type="DB_VENDOR" />
    

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

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

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

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

    public interface DatabaseIdProvider {
      default void setProperties(Properties p) { // 从 3.5.2 开始,该方法为默认方法
        // 空实现
      }
      String getDatabaseId(DataSource dataSource) throws SQLException;
    }
    

    9.mappers(映射文件)

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

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

    细节将在映射文件加载流程中会详细谈。

    二、重要类与对象说明

    • SqlSessionFactoryBuilder

    SqlSessionFactoryBuilder 有9种build() 方法,每一种都允许你从不同的资源中创建一个 SqlSessionFactory 实例。

    public SqlSessionFactory build(Reader reader);
    public SqlSessionFactory build(Reader reader, String environment);
    public SqlSessionFactory build(Reader reader, Properties properties);
    public SqlSessionFactory build(Reader reader, String environment, Properties properties);
    public SqlSessionFactory build(InputStream inputStream);
    public SqlSessionFactory build(InputStream inputStream, String environment);
    public SqlSessionFactory build(InputStream inputStream, Properties properties);
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) ;
    public SqlSessionFactory build(Configuration config);
    
    • XMLConfigBuilder

    专门用来解析全局配置文件的解析器

    • XpathParser

    使用XPATH解析XML配置文件

    • Configuration对象

    MyBatis框架支持开发人员通过配置文件与其进行交流.在配置文件所配置的信息,在框架运行时,会被XMLConfigBuilder解析并存储在一个Configuration对象中.Configuration对象会被作为参数传送给DeFaultSqlSessionFactory.而DeFaultSqlSessionFactory根据Configuration对象信息为Client创建对应特征的SqlSession对象(Configuration 类包含了对一个 SqlSessionFactory 实例你可能关心的所有内容)。

        protected Environment environment;
        // 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。默认为false
        protected boolean safeRowBoundsEnabled;
        // 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false。
        protected boolean safeResultHandlerEnabled = true;
        // 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN
        // 到经典 Java 属性名 aColumn 的类似映射。默认false
        protected boolean mapUnderscoreToCamelCase;
        // 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。默认值false (true in ≤3.4.1)
        protected boolean aggressiveLazyLoading;
        // 是否允许单一语句返回多结果集(需要兼容驱动)。
        protected boolean multipleResultSetsEnabled = true;
        // 允许 JDBC 支持自动生成主键,需要驱动兼容。这就是insert时获取mysql自增主键/oracle sequence的开关。
        // 注:一般来说,这是希望的结果,应该默认值为true比较合适。
        protected boolean useGeneratedKeys;
        // 使用列标签代替列名,一般来说,这是希望的结果
        protected boolean useColumnLabel = true;
        // 是否启用缓存
        protected boolean cacheEnabled = true;
        // 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,
        // 这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。
        protected boolean callSettersOnNulls;
        // 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的工程必须采用Java 8编译,
        // 并且加上-parameters选项。(从3.4.1开始)
        protected boolean useActualParamName = true;
        //当返回行的所有列都是空时,MyBatis默认返回null。 当开启这个设置时,MyBatis会返回一个空实例。
        // 请注意,它也适用于嵌套的结果集 (i.e. collectioin and association)。(从3.4.2开始)
        // 注:这里应该拆分为两个参数比较合适, 一个用于结果集,一个用于单记录。
        // 通常来说,我们会希望结果集不是null,单记录仍然是null
        protected boolean returnInstanceForEmptyRow;
        // 指定 MyBatis 增加到日志名称的前缀。
        protected String logPrefix;
        // 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。一般建议指定为slf4j或log4j
        protected Class<? extends Log> logImpl;
        // 指定VFS的实现, VFS是mybatis提供的用于访问AS内资源的一个简便接口
        protected Class<? extends VFS> vfsImpl;
        // MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。
        // 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。
        // 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
        protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
        // 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,
        // 多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。
        protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
        // 指定对象的哪个方法触发一次延迟加载。
        protected Set<String> lazyLoadTriggerMethods = new HashSet<>(
                Arrays.asList("equals", "clone", "hashCode", "toString"));
        // 设置超时时间,它决定驱动等待数据库响应的秒数。默认不超时
        protected Integer defaultStatementTimeout;
        // 为驱动的结果集设置默认获取数量。
        protected Integer defaultFetchSize;
        // SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);
        // BATCH 执行器将重用语句并执行批量更新。
        protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
        // 指定 MyBatis 应如何自动映射列到字段或属性。
        // NONE 表示取消自动映射;
        // PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。
        // FULL 会自动映射任意复杂的结果集(无论是否嵌套)。
        protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
        // 指定发现自动映射目标未知列(或者未知属性类型)的行为。这个值应该设置为WARNING比较合适
        protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
        // settings下的properties属性
        protected Properties variables = new Properties();
        // 默认的反射器工厂,用于操作属性、构造器方便
        protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
        // 对象工厂, 所有的类resultMap类都需要依赖于对象工厂来实例化
        protected ObjectFactory objectFactory = new DefaultObjectFactory();
        // 对象包装器工厂,主要用来在创建非原生对象,比如增加了某些监控或者特殊属性的代理类
        protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
        // 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。
        protected boolean lazyLoadingEnabled = false;
        // 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。MyBatis 3.3+使用JAVASSIST
        protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
        // MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
        protected String databaseId;
        /**
         * Configuration factory class. Used to create Configuration for loading
         * deserialized unread properties.
         *
         * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue
         *      300 (google code)</a>
         */
        protected Class<?> configurationFactory;
    
        protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
    
        // mybatis插件列表
        protected final InterceptorChain interceptorChain = new InterceptorChain();
        protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
    
        // 类型注册器, 用于在执行sql语句的出入参映射以及mybatis-config文件里的各种配置
        // 比如<transactionManager type="JDBC"/><dataSource type="POOLED">时使用简写, 后面会详细解释
        protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
        protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
    
        protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(
                "Mapped Statements collection")
                        .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource()
                                + " and " + targetValue.getResource());
        protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
        protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
        protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
        protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
    
        protected final Set<String> loadedResources = new HashSet<>();
        protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
    
        protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
        protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
        protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
        protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
    
        /*
         * A map holds cache-ref relationship. The key is the namespace that references
         * a cache bound to another namespace and the value is the namespace which the
         * actual cache is bound to.
         */
        protected final Map<String, String> cacheRefMap = new HashMap<>();
    
        public Configuration(Environment environment) {
            this();
            this.environment = environment;
        }
    
        public Configuration() {
            //TypeAliasRegistry(类型别名注册器)
            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);
        }
    
    • SqlSessionFactory 接口

    默认实现类是DefaultSQLSessionFactory类

      SqlSession openSession();
    
      SqlSession openSession(boolean autoCommit);
      SqlSession openSession(Connection connection);
      SqlSession openSession(TransactionIsolationLevel level);
    
      SqlSession openSession(ExecutorType execType);
      SqlSession openSession(ExecutorType execType, boolean autoCommit);
      SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
      SqlSession openSession(ExecutorType execType, Connection connection);
    
      Configuration getConfiguration();
    

    三、加载全局配置文件流程

    • 入口:SqlSessionFactoryBuilder#build

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          // XMLConfigBuilder:用来解析XML配置文件
          // 使用构建者模式
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          // parser.parse():使用XPATH解析XML配置文件,将配置文件封装为Configuration对象
          // 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            inputStream.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }
    

    流程分析图

    加载全局配置文件流程.png

    源码阅读

    • XMLConfigBuilder#构造参数

    // XMLConfigBuilder:用来解析XML配置文件
    // 使用构建者模式
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    

    具体实现

    XMLConfigBuilder#构造参数.png

    XmlConfigBuilder#构造函数

    XMLConfigBuilder:用来解析XML配置文件(使用构建者模式)

     public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
      }
    

    1. XpathParser#构造函数

    用来使用XPath语法解析XML的解析器

    public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
        commonConstructor(validation, variables, entityResolver);
        // 解析XML文档为Document对象
        this.document = createDocument(new InputSource(inputStream));
      }
    

    1.1 XPathParser#createDocument

    解析全局配置文件,封装为Document对象(封装一些子节点,使用XPath语法解析获取)

    private Document createDocument(InputSource inputSource) {
        // important: this must only be called AFTER common constructor
        try {
          DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
          // 进行dtd或者Schema校验
          factory.setValidating(validation);
    
          factory.setNamespaceAware(false);
          // 设置忽略注释为true
          factory.setIgnoringComments(true);
          // 设置是否忽略元素内容中的空白
          factory.setIgnoringElementContentWhitespace(false);
          factory.setCoalescing(false);
          factory.setExpandEntityReferences(true);
    
          DocumentBuilder builder = factory.newDocumentBuilder();
          builder.setEntityResolver(entityResolver);
          builder.setErrorHandler(new ErrorHandler() {
            @Override
            public void error(SAXParseException exception) throws SAXException {
              throw exception;
            }
    
            @Override
            public void fatalError(SAXParseException exception) throws SAXException {
              throw exception;
            }
    
            @Override
            public void warning(SAXParseException exception) throws SAXException {
            }
          });
          // 通过dom解析,获取Document对象
          return builder.parse(inputSource);
        } catch (Exception e) {
          throw new BuilderException("Error creating document instance.  Cause: " + e, e);
        }
      }
    

    2. XMLConfigBuilder#构造函数

    创建Configuration对象,同时初始化内置类的别名

    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        //  创建Configuration对象,并通过TypeAliasRegistry注册一些Mybatis内部相关类的别名
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
      }
    

    2.1Configuration#构造函数

    创建Configuration对象,同时初始化内置类的别名

    public Configuration() {
            //TypeAliasRegistry(类型别名注册器)
            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);
        }
    
    • XMLConfigBuilder#parse

    //使用XPATH解析XML配置文件,将配置文件封装为Configuration对象
    parser.parse();
    

    具体实现

    XMLConfigBuilder#parse.png

    XMLConfigBuilder#parse

    解析XML配置文件

    /**
       * 解析XML配置文件
       * @return
       */
      public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        // parser.evalNode("/configuration"):通过XPATH解析器,解析configuration根节点
        // 从configuration根节点开始解析,最终将解析出的内容封装到Configuration对象中
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
    

    1. XPathParser#evalNode(xpath语法)

    XPath解析器,专门用来通过Xpath语法解析XML返回XNode节点

    public XNode evalNode(String expression) {
        // 根据XPATH语法,获取指定节点
        return evalNode(document, expression);
      }
    
      public XNode evalNode(Object root, String expression) {
        Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
        if (node == null) {
          return null;
        }
        return new XNode(this, node, variables);
      }
    

    2. XMLConfigBuilder#parseConfiguration(XNode)

    从configuration根节点开始解析,最终将解析出的内容封装到Configuration对象中

    private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          // 解析</properties>标签
          propertiesElement(root.evalNode("properties"));
          // 解析</settings>标签
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          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"));
          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);
        }
      }
    
    • SqlSessionFactoryBuilder#build

    返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)

    // 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
    return build(parser.parse());
    
    public SqlSessionFactory build(Configuration config) {
        // 创建SqlSessionFactory接口的默认实现类
        return new DefaultSqlSessionFactory(config);
      }
    

    总结

    1. SqlSessionFactoryBuilder去创建SqlSessionFactory时,需传入一个Configuration对象
    2. XMLConfigBuilder会去实例化Configuration对象
    3. XMLConfigBuilder会去初始化Configuration对象

    • 通过XPathParser去解析全局配置文件,想成Document对象
    • 通过XPathParser去获取指定节点的XNode对象
    • 解析Xnode对象的信息,然后封装到Configuration对象中

    相关文章

      网友评论

          本文标题:MyBatis源码阅读【加载】(一)全局配置文件的加载

          本文链接:https://www.haomeiwen.com/subject/tlqljltx.html