我猜测你最开始学习mybatis时肯定是通过XML来构建SqlSessionFactory对象的,或许有些迷茫,来看一下这行代码。
InputStream inputStream = Resources.getResourceAsStream("myBatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
复制代码
上面的代码就是构造出sqlSessionFactory的代码,通过sqlSessionFactory来创建我们的sqlSession对象,进而进行数据库操作。包括官网文档的入门文档中也是首先介绍了通过XML方式构建SqlSessionFactory。我们可以写一个单元测试来一起看一下,mybatis是如何通过XML来构建SqlSessionFactory对象的。
首先我们看一下SqlSessionFactoryBuilder类内部的结构,原来SqlSessionFactoryBuilder多次重载build方法利用建造者模式来创建我们最终的DefaultSqlSessionFactory对象。
我将类内代码精简一下,更加清楚的看清调用过程。
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
// 1. 调用重载方法,进行实际操作。
return build(inputStream, null, properties);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 2. 将xml配置文件转换成XMLConfigBuilder对象,初始化XpathParse
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 3. 利用Xpath进行解析,返回Configration对象,创建DefaultSqlSessionFactory对象
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.
}
}
}
public SqlSessionFactory build(Configuration config) {
// 4. 创建DefaultSqlSessionFactory对象,并返回。
return new DefaultSqlSessionFactory(config);
}
复制代码
先不讨论细节,大家对上面代码每一步的功能有个大致的了解。总结一下就是: 读取xml配置文件 -> 将输入流转换为可操作对象 -> 将可操作的对象进行解析,得到配置文件对象 -> 根据配置文件内的配置新建SqlSessionFactory对象 。这里的配置文件对象就可以理解为xml配置文件以一种java能直接操作的形式存在。
先不要关注XMLConfigBuilder是怎么来的,大家先记住这对象里面有个和 XPath 相关的对象来进行实际解析操作即可。我将代码贴出来,咱们天才一步一步看。
首先我们调用了 XMLConfigBuilder 类内的 parse() 方法获取Configuration对象。
public Configuration parse() {
// 1. 验证这个对象没有被多次解析过,parsed是在类内第一行定义的一个布尔变量。
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 2. 调用神秘方法,继续解析。
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
复制代码
在这里大家一定好奇 parser.evalNode("/configuration") 这句话。好吧,避不开了,先说一下 parser 这个东西是怎么来的。上一节构建sqlSessionFactory中的代码 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties) 这行代码主要进行了一些初始化工作,最重要的就是初始化了XPathParser对象,这个对象就是上面提到的XPath相关。 通过XPathParser进行了配置文件中每个结点的解析 。下面就是代码,大家简单看一下就好,反正重点也不在这里。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
// 1. 初始化XPathParser对象然后调用构造方法初始化XMLConfigBuilder对象,这个对象就是parser.evalNode("/configuration")的parse对象。
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//2. 新建一个Configration对象,接收xml配置
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
// 这个parser就是parser.evalNode("/configuration")的parse对象。
this.parser = parser;
}
复制代码
evalNode("/configuration")这个方法是XPathParser类内的方法,贴了个简单的代码,大概来说就是传入的参数是xml配置文件中的一个结点,解析配置文件后得到这个结点的XNode对象(理解为配置文件的java可操作的形式即可)。
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
复制代码
我们了解了XNode对象之后再来看神秘方法:parseConfiguration方法,看名字就知道了,他才是解析configration的真正入口
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//issue #117 read properties first
// <1> 解析 <properties /> 标签
propertiesElement(root.evalNode("properties"));
// <2> 解析 <settings /> 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// <3> 加载自定义 VFS 实现类
loadCustomVfs(settings);
// <4> 解析 <typeAliases /> 标签
typeAliasesElement(root.evalNode("typeAliases"));
// <5> 解析 <plugins /> 标签
pluginElement(root.evalNode("plugins"));
// <6> 解析 <objectFactory /> 标签
objectFactoryElement(root.evalNode("objectFactory"));
// <7> 解析 <objectWrapperFactory /> 标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// <8> 解析 <reflectorFactory /> 标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// <9> 赋值 <settings /> 到 Configuration 属性
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// <10> 解析 <environments /> 标签
environmentsElement(root.evalNode("environments"));
// <11> 解析 <databaseIdProvider /> 标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// <12> 解析 <typeHandlers /> 标签
typeHandlerElement(root.evalNode("typeHandlers"));
// <13> 解析 <mappers /> 标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
复制代码
看注释就能了解到,这个方法是真正解析配置文件内各个标签的真正入口,感兴趣的可以自行看一下里面的内容。在这个方法内依次调用类内的结点解析方法,丰富我们的configration对象。
这边文章写的比较简单,大致了解了mybatis是如何解析配置的。最后我们总结一下:
1. 在SqlSessionFactory的build方法中,传入xml配置文件,利用XMLConfigBuilder解析配置。 2. 在初始化XMLConfigBuilder对象的过程中:初始化了Configration对象用于接收xml配置、初始化XPathParser对象用于解析xml配置文件。 3. 由XPathParser对象对象解析xml配置文件,获得根结点configration XNode。 4. 将configration XNode作为参数,调用parseConfiguration依次解析内部子结点,将解析后的结果存入Configratuon对象中。 5. 集各项配置于一身的Configration对象来初始化出SqlSessionFactory。复制代码