前言:通过前面两篇文章,我们对MyBatis有了初步的了解。从这篇开始,我会对MyBatis从初始化到执行查询的步骤进行源码的剖析。首先涉及到的就是初始化了。有疑惑可以回到《MyBatis学习笔记(一)》。
一、从代码入手看MyBatis初始化做了什么工作
public class MyBatisUtil {
private static final SqlSessionFactory sqlSessionFactory;
static{
String resource="mybatis-config.xml";
Reader reader=null;
try {
reader=Resources.getResourceAsReader(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
}
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}
不难看出,上述代码声明了MyBatis配置文件的名称mybatis-config.xml,使用Resources的静态方法获取配置文件的输入流。有兴趣的话可以追溯一下源码看看。这里重点看一下这一条语句。
sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
点击进去查看SqlSessionFactoryBuilder的build()方法。
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder e = new XMLConfigBuilder(reader, environment, properties);
var5 = this.build(e.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException var13) {
;
}
}
return var5;
}
先看第一条语句:
XMLConfigBuilder e = new XMLConfigBuilder(reader, environment, properties);
设想我们来设计MyBatis,那么在初始化的时候我们应该做什么呢?显然是解析配置文件和Mapper映射文件(mybatis-config.xml和*mapper.xml)。MyBatis的初始化也正是做了这一步。具体的代码细节涉及到了XML的解析步骤和复杂的类结构,这里只抓住主要思想。
- XML的解析需要除了需要待解析的XML文件,还需要对应的DTD文件。
- 配置文件和Mapper文件通过字节流和字符流,进而被封装进了InputSource对象(InputSource是一个封装输入源的对象),最后被组织成了Document的结构化数据。
- DTD文件同理,在XMLMapperEntityResolver中被组织成了结构化数据。
- XPathParser组合了Document对象和XMLMapperEntityResolver对象,也即是有了解析XML文件的基础。XPathParser的作用是提供根据XPath表达式获取基本的DOM节点Node信息的操作。
在看第二条语句:
var5 = this.build(e.parse());
这里主要是调用了XMLConfigBuilder的parse()方法。主要的调用链如下:
public Configuration parse() {
if(this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
private void parseConfiguration(XNode root) {
try {
Properties e = this.settingsAsPropertiess(root.evalNode("settings"));
this.propertiesElement(root.evalNode("properties"));
this.loadCustomVfs(e);
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"));
this.settingsElement(e);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
XMLConfigBuilder的parse()方法中有一句this.parser.evalNode("/configuration"),这里其实就是利用了上面讲到的XPathParser对象解析XML文件的<configuration>节点(注意我们传入的文件是mybatis-config.xml配置文件,其根节点正是<configuration>)。解析出的<configuration>节点作为整个DOM的根节点被传给了XMLConfigBuilder的parseConfiguration()方法。依次解析<settings>、<properties>...<mappers>节点。
接下来以别名节点<typeAliases>为例看看这里到底做了什么?
this.typeAliasesElement(root.evalNode("typeAliases"));
private void typeAliasesElement(XNode parent) {
if(parent != null) {
Iterator i$ = parent.getChildren().iterator();
while(i$.hasNext()) {
XNode child = (XNode)i$.next();
String alias;
if("package".equals(child.getName())) {
alias = child.getStringAttribute("name");
this.configuration.getTypeAliasRegistry().registerAliases(alias);
} else {
alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class e = Resources.classForName(type);
if(alias == null) {
this.typeAliasRegistry.registerAlias(e);
} else {
this.typeAliasRegistry.registerAlias(alias, e);
}
} catch (ClassNotFoundException var7) {
throw new BuilderException("Error registering typeAlias for \'" + alias + "\'. Cause: " + var7, var7);
}
}
}
}
可以看到首先对子节点的类型进行区分,对于<typeAlias>子节点,获取其别名<alias>和类型<type>属性的值之后,加载type类,并将别名和类型的映射关系存进了TypeAliasRegistry对象中,它其实是一个Map的映射结构。而从XMLConfigBuilder的父类BaseBuilder的构造器中我们可以看到,其实这个TypeAliasRegistry对象是Configuration对象中结合的对象。也就是说,<typeAliases>节点中的信息最终是被存入了Configuration对象中,其他的<configuration>子节点也是同理的。
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
总之,调用XMLConfigBuilder的parse()方法会把配置文件中的信息全部存入Configuration对象中。
回到刚才的语句中,继续调用SqlSessionFactoryBuilder的build()方法:
var5 = this.build(e.parse());
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
这里做的只是将Configuration对象作为参数传递给DefaultSQLSessionFactory的构造器,并将SQLSessionFactory对象返回。
小结:MyBatis的初始化首先是将配置文件、Mapper文件和DTD文件作为输入源分别组织成Document和XMLMapperEntityResolver这种结构化的数据,再封装进XPathParser中,由XPathParser对外部提供节点的解析能力。最终,XML中的各种信息被解析并存储进了一个Configuration对象中,同时返回了一个封装了该Configuration的SQLSessionFactory对象。
二、Configuration对象中存储了哪些信息
上面我们说到MyBatis配置文件中的信息最终被存入了一个Configuration对象。为了后续学习的方便,我们将Configuration对象中存的信息罗列出来。可以参考这段代码:
private void parseConfiguration(XNode root) {
try {
Properties e = this.settingsAsPropertiess(root.evalNode("settings"));
this.propertiesElement(root.evalNode("properties"));
this.loadCustomVfs(e);
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"));
this.settingsElement(e);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
- settings设置
- properties属性
- typeAliases类型别名
- plugins插件
- objectFactory对象工厂
- objectWrapperFactory
- reflectFactory
- databaseProvider
- mappers映射器
- environments
- environment
- transactionManager
- dataSource
三、涉及到的设计模式
- Builder模式
记得我们构造SQLSessionFactory的方式,从代码上就可以看出是Builder模式:
new SqlSessionFactoryBuilder().build(reader);
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder e = new XMLConfigBuilder(reader, environment, properties);
var5 = this.build(e.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException var13) {
;
}
}
return var5;
}