持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 18 天,点击查看活动详情
我们在使用 MyBatis 与数据库进行交互时,有两种方式,一种传统方式,一种 mapper 代理方式。接下来我们会分别针对这两种方式都进行下源码剖析。
我们通过对传统方式的源码剖析要去知道 MyBatis 的底层它是如何去加载、解析配置文件的以及是如何去执行 SQL 语句、设置参数以及封装返回结果集。
然后,我们会再对 mapper 代理方式进行源码剖析。我们通过这一部分源码剖析,要去知道 MyBatis 底层是怎么去产生代理对象,当代理对象调用方法时,它又是怎么去执行到底层的 JDBC 代码。
接下来我们会分别针对这两种方式都进行下源码剖析。
在源码剖析之前,大家可以回忆一下,我们在第一节完成的自定义持久层框架。当时在编写自定义持有层框架的时候,已经介绍它就是 MyBatis 的一个雏形。如果现在大家已经能够知道这个雏形是怎么形成,其实对于我们现在翻阅源码是很有帮助的。
不管现在是传统方式,还是 mapper 代理方式,加载配置文件和初始化过程都是一样的。
读取配置文件
在 MyBatis 初始化过程中,MyBatis 会读取 mybatis-config.xml 核心配置文以及所有的mapper.xml 映射配置文件,同时还加载这两个配置文件的指定类以及解析类中相关注解,最终解析得到的内容转成配置对象,加载完成之后,MyBatis 会根据配置对象初始化各个模块。
首先需要加载核心配置文件,就需要传入核心配置文件的路径。读取配置文件,读成字节输入流,但是要注意要注意,现在还没解析,只是把它加载成了字节输入流。
//读取配置文件,读成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
Resources.getResourceAsStream() 方法进行源码剖析,在这个方法中,所要完成的功能就是去调用了 getResourceAsStream() 重载方法。在调用这个重载方法时,需要传递两个参数,分别是 null 值和核心配置文件的路径。
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource);
}
在这个 getResourceAsStream() 方法中,执行的方法就是 classLoaderWrapper.getResourceAsStream() 方法,其实还是去借助类加载器 getResourceAsStream() 方法来根据当前配置文件路径,把它加载成字节输入流进行返回。
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
初始化
这一步才是初始化的关键。
通过 new SqlSessionFactoryBuilder() 方法去调用 build() 的方法,其实 build() 的方法才是初始化工作的一个真正开始。读取配置文件只是一个工具类,把配置文件加载成字节输入流,把加载出来的字节输入流传入到 build() 方法中,并且拿到返回结果是一个 sqlSessionFactory。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
在剖析 MyBatis 之前,先来回顾之前我们在自定义持久层框架,介绍过 build() 方法要完成两个功能。第一个功能,它要去解析我们的解析核心配置文件,把它解析出来的内容封装成一个consideration 对象。第二个功能,创建 SqlSessionFactory 实现类对象。
在 MyBatis 框架中,build() 方法是不是这么实现的。首先,在 build() 方法调用了重载方法 build,传递了三个参数,分别是 inputStream、environment 和 properties,其中inputStream 是传入加载的字节流, environment 和 properties 的值都为 null。
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
在这个 build 重载方法中,同样完成两个功能,第一个功能就是解析配置文件,封装成 consideration。第二功能就是创建 SqlSessionFactory 的实现类对象。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
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.
}
}
}
先来看一下解析配置文件的步骤。
第一步,创建 XMLConfigBuilder 对象,这个对象的作用就是专门用来解析 MyBatis 配置文件类,通过 XMLConfigBuilder 类来解析 MyBatis 配置文件。parse() 方法就是真正的去执行 XML 解析了。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
在这个 parse() 方法中,首先使用 if 进行判断,判断当前配置文件有没有解析过,如果解析过,parsed 的值为 true,就会抛出 BuilderException 异常。如果没有解析过,parsed 值默认是 false,跳过 if 判断,继续往下执行。
第一次加载肯定是没有解析过的,所以继续往下执行,对配置文件进行解析。首先解析之前,先将 parsed 标记设置为 true。这样表示当前配置文件已经解析过的。
具体解析配置文件中的内容需要调用 parseConfiguration() 方法。在调用 parseConfiguration() 方法时,传递一个 parser.evalNode("/configuration") 参数,其中,这个 parser 就是 XPathParser 解析器对象,evalNode 方法就是用来读取节点的数据,读取就是 configuration 这个顶层标签,读取到这个 configuration 顶层标签之后,把这个标签当做参数传入到 parseConfiguration() 方法中,执行 parseConfiguration() 方法。
parseConfiguration() 方法定义了解析 mybatis-config.xml 配置文件的完整流程。
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
通过 parseConfiguration() 方法源码 ,我们可以清晰地看到 XMLConfigBuilder 对 mybatis-config.xml 配置文件中各类标签的解析方法。解析核心步骤如下:
-
解析 标签;
-
解析 标签;
-
解析
<typeAliases>标签; -
解析
<plugins>标签; -
解析 标签;
-
解析 标签;
-
解析
<reflectorFactory>标签; -
解析
<environments>标签; -
解析
<databaseIdProvider>标签; -
解析
<typeHandlers>标签; -
解析
<mappers>标签。