前言
任何框架的初始化,无非都是加载自己运行时所需要的配置信息,MyBatis 也不例外。那究竟是如何加载 XML 配置文件的呢,现在来探究一下。
主要流程
按照 MyBatis 的使用流程来看,大体上可以划分为以下三步。
- 加载配置文件,初始化 SqlSessionFactory;
- 获取 SqlSession 和 Mapper;
- 执行 Mapper 方法。
而第 1 步和第 2 步可以封装为一个工具类:
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* @author yyt
* @date 2020年02月04日 14:31
* 工具类
*/
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
// 获取 SqlSessionFactory 的实例
static {
try {
String resource = "config/mybatis-config.xml";
// 读取 XML 核心配置文件得到输入流对象
InputStream inputStream = Resources.getResourceAsStream(resource);
// SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 从 SqlSessionFactory 中获取 SqlSession
*
* @return org.apache.ibatis.session.SqlSession
* @author yyt
* @date 2020/2/4 14:42
*/
public static SqlSession getSqlSession() {
// 设置 autoCommit 为 true,自动提交事务
return sqlSessionFactory.openSession(true);
}
}
构建 SqlSessionFactory
SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
String resource = "config/mybatis-config.xml";
// 读取 XML 核心配置文件得到输入流对象
InputStream inputStream = Resources.getResourceAsStream(resource);
MyBatis 包含一个名叫 Resources 的工具类,调用它的 getResourceAsStream() 的方法,加载配置 MyBatis 的核心配置文件,得到一个输入流对象。查看 Resources
类的源码中对应的方法:
可以看到在该方法内部调用了同名的重载方法,其中得到输入流对象的是这一句:
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
查看完整的源码可知,ClassLoaderWrapper
类中有一个方法 getClassLoaders(ClassLoader classLoader)
,返回结果为一个 ClassLoader[] 数组,该数组指明了类加载器的使用顺序。查看 ClassLoaderWrapper
类的源码中对应的方法:
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return this.getResourceAsStream(resource, this.getClassLoaders(classLoader));
}
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
// 参数传入的指定的默认类加载器,这里传入 null
classLoader,
// 系统指定的默认类加载器
this.defaultClassLoader,
// 当前线程绑定的类加载器
Thread.currentThread().getContextClassLoader(),
// 当前类所使用的类加载器
this.getClass().getClassLoader(),
// 系统类加载器
this.systemClassLoader
};
}
在对应的 getResourceAsStream()
方法中可以看到,它的底层是调用了 java.lang 包下的类加载器 ClassLoader 的 getResourceAsStream(String name)
方法。
得到了输入流对象 inputStream,接着往下执行:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
首先是 new 一个 SqlSessionFactoryBuilder 对象,然后调用它的 build(inputStream)
方法。
点击查看 SqlSessionFactoryBuilder
类中对应的 build(InputStream inputStream)
方法:
MyBatis 会先去创建一个 XMLConfigBuilder 解析器对象,去解析传进来的那个输入流对象,解析完成之后,返回一个 Configuration 对象,这个对象里面封装了解析之后我们配置的信息。
继续点击查看 XMLConfigBuilder
类对应的构造方法:
SqlSessionFactoryBuilder 调用 XMLConfigBuilder 对象的 parse() 方法,在没有解析过的前提下,会调用 parseConfiguration(XNode root)
方法去完成 Configuration 对象的构建,这个 Configuration 对象并不是 XMLConfigBuilder 的属性,而是父类 BaseBuilder 抽象类的 protected 属性。
private void parseConfiguration(XNode root) {
try {
// 解析 XML 文件中的标签顺序和配置的顺序一致
this.propertiesElement(root.evalNode("properties"));
// 将 settings 标签的配置转换为 Properties 标签
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
// 根据配置设置访问资源文件的方式
this.loadCustomVfs(settings);
// 根据配置设置日志处理的方式
this.loadCustomLogImpl(settings);
// 别名
this.typeAliasesElement(root.evalNode("typeAliases"));
// 插件
this.pluginElement(root.evalNode("plugins"));
// 对象工厂
this.objectFactoryElement(root.evalNode("objectFactory"));
// 对象包装工厂
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 反射工厂
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 通过 settings 配置初始化全局配置
this.settingsElement(settings);
// 多环境配置
this.environmentsElement(root.evalNode("environments"));
// 数据库类型标志
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 类型转换器
this.typeHandlerElement(root.evalNode("typeHandlers"));
// 注册 Mapper XML 映射文件
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
在 XMLConfigBuilder 对象执行限于篇幅,这里不展开详细说明如何创建 XNode 对象以及如何使用 XPath 表达式解析节点。
parse()
方法返回 Configuration 对象后,SqlSessionFactoryBuilder 根据这个 Configuration 对象执行了 build(Configuration config)
方法,创建一个 DefaultSessionFactory 对象:// 返回一个实现了 SqlSessionFactory 接口的 DefaultSqlSessionFactory 对象
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
所以,SqlSessionFactoryBuilder 返回一个 DefaultSessionFactory 对象给 Client,供 Client 使用。
获取 SqlSession
既然有了 SqlSessionFactory,顾名思义,我们可以通过调用 openSession() 方法从中获得 SqlSession 的实例。在这个过程中需要通过 TransactionFactory 生成 Transaction 对象,并且还需要创建核心执行器 Executor 对象。基于这些条件,最终创建了实现 SqlSession 接口的 DefaultSqlSession 对象。
public static SqlSession getSqlSession() {
// 设置 autoCommit 为 true,自动提交事务
return sqlSessionFactory.openSession(true);
}
点击打开 DefaultSqlSessionFactory
类查看对应的方法:
public SqlSession openSession(boolean autoCommit) {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
// 获取环境配置信息
Environment environment = this.configuration.getEnvironment();
// 获取 environment 节点下的事务配置信息,默认情况下是 ManagedTransactionFactory;
// 如果配置了 transactionManager 节点并且 type 为 JDBC,则返回 JdbcTransactionFactory 事务工厂
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
// 通过事务工厂来新建一个事务,设置数据源、事务等级、是否自动提交
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 生成一个执行器对象(事务包含在里面)
Executor executor = this.configuration.newExecutor(tx, execType);
// 新建一个 DefaultSqlSession
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
// 如果打开事务出错,则关闭事务
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
总结
参考
知乎专栏 - Mybatis源码详解系列
稀土掘金 - SessionFactory机制原理