配置文件的解析
前面几节简略讲了怎么一步步组装sql语句,一步步往jdbc写法靠近。其中,也留了很多问题。比如
SqlSessionFactory和sqlSession是怎么构建的?
configuration中拿出包含sql的statement是什么时候初始化的?
一些hander为什么是默认的?如果不要默认在哪里配置?
等等。
为了上述问题,我们来看看这么东西是怎么来的。
在这之前,需要一些预备知识。
XPATH。
推荐了解文章:http://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true); // never forget this!
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(inputStream));
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
Object config = xpath.evaluate("/configuration",doc, XPathConstants.NODE);
Node node = (Node) config;
System.out.println(node);
以上代码片段不是源码,是我写的xpath方式解析xml的小例子。
大致步骤分为:
第一环境:建立文件流InputStream–>新建DocumentBuilderFactory–>拿到DocumentBuilder–>再拿到Document 对象
第二环境:创建XPathFactory–>拿到XPath.
主要实现:通过上述两个前提执行查找方法。
Object config = xpath.evaluate(“/configuration”,doc, XPathConstants.NODE);
Node node = (Node) config;
这样就能查新到以configuration节点。
这个xpath解析方法就是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">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/erik?useUnicode=true&characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="yushu"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/erik/PersonMapper.xml"/>
</mappers>
</configuration>
我们回过头来看看SqlSessionFactory和SqlSession的创建。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//创建SqlSession
SqlSession session = sqlSessionFactory.openSession();
前面两行是创建文件流,和例子中的是一样的,关键在第三和第四两行。
在查看这两行代码前,先给的整体概念:
SqlSessionFactoryBuilder:SqlSessionFactory的创建类,里面包含了各种入参的build方法。
XMLConfigBuilder:这个可不是创建XMLConfig类的(也没这个类),这个类的作用是里面有一大堆parse方法。把xml里面的配置信息写到相对应的配置对象中。
XPathParser:对XPath的进一步封装,主要是里面的evaluate等方法查找xml中相关的节点。
Configuration:mybatis中大而全的配置对象,里面包含了几乎所以的配置信息。
读取配置文件的工作模式是XPathParser解析xml,返回的东西交给XMLConfigBuilder放到Configuration中,然后用Configuration创建SqlSessionFactoryBuilder.
现在看下build方法。
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
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.
}
}
}
首先通过构造方法使用输入流,环境,以及properties文件来创建XMLConfigBuilder对象。再调用parse方法解析配置文件内容,返回Configration对象中,再使用SqlSessionFactory的build方法创建SqlSessionFactory对象。
这里的主要方法为parse方法。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parsed对象控制了每一个XMLConfigBuilder只执行一次parse方法。
parseConfiguration方法传入了跟节点(/configuration的写法意思为从根节点开始选取configuration,具体请参见xpath的写法)
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
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);
}
}
我们可以看到,这里已经开始解析各个节点的内容了。
至于各个节点什么意思,可以到mybatis官方说明文档中看看(http://mybatis.github.io/mybatis-3/zh/configuration.html)
对照着相应的配置查看。
我们先看下第一个解析
propertiesElement(root.evalNode(“properties”));
root.evalNode(“properties”),这里的root指的是上文中得到的configuration,这句的意思是在configuration下查找properties节点。
properties属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递,更多请参考官方文档。
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
properties在配置文件中的形式是这样的。
再来看看解析的代码:
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
这里的context就是上文中查找的properties节点了。
我们看下逻辑:
拿到properties的子节点,存放在defaults中。
再获取properties节点的resource和url属性,先取resource中的属性值,再取url中的属性值,再获取configuration中的变量,然后都放到defaults去,最后更新configuration的Variables。
这些代码,我们能得到一些信息:
1.properties支持在资源文件中配置,也支持在url中配置,两个一起配置,只会资源文件生效。
2.属性值的优先关系。最后放置的最高,因为不会被其他覆盖,我们可以看到,configuration.getVariables();是最后放的,这个的第一次初始化是在创建XMLConfigBuilder时,传入了一个Propertis对象创建的,也就是说创建SqlSessionFactory创建时,传入的属性值优先级最高。
其次是resourse或者url的属性,最低的优先级就是这个配置文件配置的字节点属性值。
这里有这么多的解析。
我想说其他的差不多。就略过了,其实不然,每个都有自己的特色,我争取每个的分析都写出来。同时也是加深对mybatis理解的过程。