2.探究 Mybatis 运行环境的准备过程

优质
小牛编辑
120浏览
2023-12-01

在前面的文章中我们已经了解了SqlSessionFactoryBuilder对象的build方法是我们学习的入口方法。

SqlSessionFactoryBuilder看起来是一个很简单的类,他的职责也很单一,就是用来创建SqlSessionFactory对象,它定义了一个build方法,并为其提供了多种重载形式以便于通过不同的途径获取SqlSessionFactory实例.

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

这些重载方法看起来很多,除却最后一个SqlSessionFactory build(Configuration config)方法之外,其余的方法本质上都是通过解析mybatis的配置文件创建一个Configuration对象,然后把这个Configuration对象委托给SqlSessionFactory build(Configuration config)方法来完成SqlSessionFactory对象的创建工作。

我们可以将除SqlSessionFactory build(Configuration config)以外的其余方法分为两大类,一类是通过字节流创建Configuration对象,一类是通过字符流创建Configuration对象。

这两类方法根据流的不同最终方法调用会落到下面两个方法之一:

  • SqlSessionFactory build(InputStream inputStream, String environment, Properties properties);
  • SqlSessionFactory build(Reader reader, String environment, Properties properties);

上面这两个方法其实很像,区别只是在于配置文件对应的文件流的具体类型一个是Reader,一个是InputStream

但是无论是Reader还是InputStream,对于我们阅读源码影响都不大,因为他们最终都会被转换成InputSource对象来供mybatis使用,

mybatis 默认使用InputSource对象封装XML输入源的信息,包括公共标识符、系统标识符、字节流(可能带有指定的编码)、基本 URI或字符流。

所以这里我们只看InputStream对应的build方法即可,顺带一提的是,其实我们可以通过Reader reader=new InputStreamReader(inputStream);来将Inputstream转换为Reader对象。

/**
 * 创建一个SqlSessionFactory
 * @see #build(InputStream, String, Properties)
 * @param inputStream Mybatis配置文件对应的字节流
 * @param environment 默认的环境ID,具体的environment的配置可以参考Mybatis配置文件内的Environments节点,默认为default
 * @param properties  指定的mybatis属性配置
 */
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // 构建一个Mybatis 主要配置文件的解析器
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // 使用默认的JDBC会话工厂
        return build(
                parser.parse()/*解析Mybatis配置*/
        );
    } 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.
        }
    }
}

方法build(InputStream, String, Properties)有三个入参:

  • inputStream表示mybatis配置文件对应的字节流,不需要过多的解释。

  • environment参数用来指定当前使用的环境标志,点击了解mybatis的多环境配置

    mybatis可以进行多环境的配置,只需要在mybatis全局配置文件的environments元素中新增多个environment节点即可,build方法的参数environment的取值就对应着environments子元素environmentid属性.

    具体到下面的事例中,我们在mybatis的配置文件中提供两套环境配置:

    <?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">
                <!-- 声明使用那种事务管理机制 JDBC/MANAGED -->
                <transactionManager type="JDBC"/>
                <!-- 配置数据库连接信息 -->
                <dataSource type="POOLED">
                  <!-- 省略研发数据库配置 -->
                </dataSource>
            </environment>
            <environment id="test">
                <!-- 声明使用那种事务管理机制 JDBC/MANAGED -->
                <transactionManager type="JDBC"/>
                <!-- 配置数据库连接信息 -->
                <dataSource type="POOLED">
                    <!-- 省略测试数据库配置 -->
                </dataSource>
            </environment>
        </environments>
       <!-- 省略其他配置 -->
    </configuration>
    

    因此我们的environment参数会有两个可用值:developmenttest

    这时,如果我们需要切换运行环境只需要在创建SqlSessionFactory对象的时候传入对应的environment参数即可。

      // 研发环境
      SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(loadStream(GLOBAL_CONFIG_FILE), "development");
    
      // 测试环境
      SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(loadStream(GLOBAL_CONFIG_FILE), "test");
    
  • properties参数是用户自定义的属性对象,这些属性对象可以在整个配置文件中以占位符的形式被读取和使用。 点击了解mybatis的属性配置

    配置文件包括:全局配置文件和mapper配置文件。

    举个简单的例子,可以将数据库相关的配置信息存放到jdbc.properties文件中,然后在mybatis的配置文件中以占位符的形式动态引用他。

    比如:

    jdbc.properties:

    jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.username=root
    jdbc.password=dUP6lKU+c3&s
    

    mybatis-global-config.xml

    <configuration>
      <environments default="development">
          <environment id="development">
              <!-- 声明使用那种事务管理机制 JDBC/MANAGED -->
              <transactionManager type="JDBC"/>
              <!-- 配置数据库连接信息 -->
              <dataSource type="POOLED">
                  <!-- 需要注意这里: MYSQL 6(含)可以使用下列配置,MYSQL 6以下还是需要使用旧的com.mysql.jdbc.Driver-->
                  <property name="driver" value="${jdbc.driver}"/>
                  <!-- 需要特殊处理 & 符号,转为&amp; -->
                  <property name="url" value="${jdbc.url}"/>
                  <property name="username" value="${jdbc.username}"/>
                  <!-- 需要特殊处理 & 符号,转为&amp; -->
                  <property name="password" value="${jdbc.password}"/>
              </dataSource>
          </environment>
      </environments>
    </configuration>
    

    稍微修改一下创建SqlSessionFactory对象时使用的参数:

    // 获取properties文件
    Properties properties = new Properties();
    properties.load("jdbc.properties文件的输入流");
    // 创建Mybatis的会话工厂
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(loadStream(GLOBAL_CONFIG_FILE), properties);
    

    这样在运行时,mybatis-global-config.xml配置文件中被${属性名}所修饰的占位符将会被动态替换成jdbc.properties文件中对应的值,比如${jdbc.driver}将会被替换成com.mysql.cj.jdbc.Driver

在了解完方法入参的含义之后,我们继续看SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)方法中的代码。

/**
 * 创建一个SqlSessionFactory
 * @see #build(InputStream, String, Properties)
 * @param inputStream Mybatis配置文件对应的字节流
 * @param environment 默认的环境ID,具体的environment的配置可以参考Mybatis配置文件内的Environments节点,默认为default
 * @param properties  指定的mybatis属性配置
 */
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // 构建一个Mybatis 主要配置文件的解析器
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // 使用默认的JDBC会话工厂
        return build(
                parser.parse()/*解析Mybatis配置*/
        );
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}

在该方法中,先创建了一个XmlConfigBuilder对象实例,这个XmlConfigBuilder对象是抽象类BaseBuilder的一个具体实现,其作用主要就是用来解析mybatis的全局配置文件。

// 构建一个Mybatis 主要配置文件的解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

BaseBuilder是mybatis定义的一个基础配置解析器,里面包含了在解析配置时常用的一些方法,虽然他是一个抽象类,但是并没有定义任何抽象方法,它所起到的作用更像是在简化多个不同的解析器之间的公共代码,因此,从这个角度上看,BaseBuilder并不是一个建造者模式的实现。

我们回头继续看XmlConfigBuilderXmlConfigBuilder提供了多种不同的构造方法,但是对外暴露的可调用的方法只有一个返回Configuration对象的parse方法。

关于Configuration对象,我们前面已经提到过了,他负责维护mybatis的配置信息,他是一个充血模式的java对象,内部包含了不少的业务逻辑,这些我们在后面会一一看到。

mybatis调用XmlConfigBuilderparse方法得到Configuration对象之后,将其转交给build(Configuration)方法来完成SqlSessionFactory对象的创建工作。

return build(parser.parse());

build(Configuration)方法中,mybatis直接调用DefaultSqlSessionFactory的构造方法就完成了SqlSessionFactory对象的创建。

/**
  * 创建一个SqlSessionFactory
  *
  * @param config 指定的Mybatis配置类
  */
 public SqlSessionFactory build(Configuration config) {
     // 构建默认的SqlSessionFactory实例
     return new DefaultSqlSessionFactory(config);
 }

我们再去看DefaultSqlSessionFactory的构造方法,会发现在构造方法内仅仅只是做了缓存Configuration对象的引用这一步操作而已,除此之外,再无其他额外的操作。

/**
 * Mybatis配置
 */
private final Configuration configuration;

public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
}

很神奇,很简单,SqlSessionFactory对象就这么被创建了,但是我们并没有看到任何关于mybatis运行环境准备工作的相关操作。

由此可见,mybatis运行环境准备工作的玄机只怕还是藏在了XMLConfigBuilder#parse()方法中。