2.探究 Mybatis 运行环境的准备过程
在前面的文章中我们已经了解了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
子元素environment
的id
属性.具体到下面的事例中,我们在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
参数会有两个可用值:development
和test
。这时,如果我们需要切换运行环境只需要在创建
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}"/> <!-- 需要特殊处理 & 符号,转为& --> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <!-- 需要特殊处理 & 符号,转为& --> <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
并不是一个建造者模式的实现。
我们回头继续看XmlConfigBuilder
,XmlConfigBuilder
提供了多种不同的构造方法,但是对外暴露的可调用的方法只有一个返回Configuration
对象的parse
方法。
关于Configuration
对象,我们前面已经提到过了,他负责维护mybatis的配置信息,他是一个充血模式的java对象,内部包含了不少的业务逻辑,这些我们在后面会一一看到。
mybatis调用XmlConfigBuilder
的parse
方法得到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()
方法中。