@
1. 簡介
在之前的文章《mybatis 初步使用(IDEA的Maven項目, 超詳細)》中, 講解了mybatis
的初步使用, 並總結了以下mybatis
的執行流程:
- 通過 Resources 工具類讀取 mybatis-config.xml, 存入 Reader;
- SqlSessionFactoryBuilder 使用上一步獲得的 reader 創建 SqlSessionFactory 對象;
- 通過 sqlSessionFactory 對象獲得 SqlSession;
- SqlSession對象通過 *Mapper 方法找到對應的 SQL 語句, 執行 SQL 查詢。
- 底層通過 JDBC 查詢后獲得 ResultSet, 對每一條記錄, 根據resultMap的映射結果映射到 Student 中, 返回 List。
- 最后記得關閉 SqlSession
本系列文章深入講解第 2 步, 解析配置文件。
2. 配置文件解析流程分析
2.1 調用
配置文件的解析過程對應的是以下的代碼:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
很簡單的兩句代碼:
- 通過
mybatis
的資源類Resources
讀入“mybatis-config.xml”
文件; - 使用
SqlSessionFactoryBuilder
類生成我們需要的SqlSessionFactory
類;(真正的解析只有這一過程)
2.2 解析的目的
要理解配置文件的解析過程, 首先要明白解析的目的是什么, 從最直觀的調用代碼來看, 是獲得SqlSessionFactory
。
但是, 從源代碼來看, 更本質的應該這么說:
mybatis解析配置文件最本質的目的是為了獲得
Configuration
對象
Configuration
對象, 可以理解是mybatis
的XML
文件在程序中的化身。
2.3 XML 解析流程
build(reader)
函數里面包含着SqlSessionFactory
的創建邏輯。
從客戶端調用build(reader)
函數到返回SqlSessionFactory
, 可以用如下的時序圖表示:
下面來看看各個步驟, 請記住,mybatis解析配置文件的本質就是獲得Configuration
對象。
2.3.1 build(parser)
其最終調用以下的方法
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
該方法:
- 創建
XMLConfigBuilder
對象; - 使用
XMLConfigBuilder
對象的方法parse()
來獲得Confiuration
對象; - 通過
build(configuration)
, 使用Confiuration
對象創建相應的SqlSessionFactory
對象。
2.3.2 new XMLConfigBuilder(...);
new XMLConfigBuilder(reader, environment, properties)
方法, 從字面上來理解就是創建一個XMLConfigBuilder
對象。
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
其最終調用的方法是這個:
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
XMLConfigBuilder
類繼承於BaseBuilder
類, super(new Configuration())
對應的方法:
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
也就是給BaseBuilder
類的各個成員變量賦值而已。
里面的XpathParser對象是通過new XPathParser(reader, true, props, new XMLMapperEntityResolver())
方法而來的。
2.3.3 new XPathParser(...)
new XPathParser(reader, true, props, new XMLMapperEntityResolver())
就是創建XpathParser
的過程。
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(reader));
}
調用了以下兩個函數:
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
注意這兩個函數是有先后順序的, createDocument
函數務必在commonConstructor
函數之后執行。
createDocument
函數, 其實就是通過 DOM 解析 XML 文件的過程中的幾個步驟,獲得document
, 具體可以參見 「mybatis 解析配置文件(一)之XML的DOM解析方式」, 里面提到了 Java 中使用 DOM 解析 XML 的步驟, 大致如下:
- 創建
DocumentBuilderFactory
對象;- 通過
DocumentBuilderFactory
創建DocumentBuilder
對象;- 通過
DocumentBuilder
, 從文件或流中創建通過Document
對象;- 創建
XPathFactory
對象, 並通過XPathFactory
創建XPath
對象;- 通過
XPath
解析出XPathExpression
對象;- 使用
XPathExpression
在文檔中搜索出相應的節點。
剛剛提到的兩個函數, 已經完成了前4部分, 獲得了Document
對象, Xpath
對象, 並返回后將其賦值給了相應的成員變量。
也就是說, 到了這一步, 我們已經獲得了XpathParser
對象, 該對象中已經含有 mybatis-config.xml 文件對應的 Document Object, 即document
和xpath
。 通過document
和xpath
,我們可以對 mybatis-config.xml 進行后兩部操作操作。
后面幾個步驟, 是在XMLConfiguration
對象的parse()
函數中使用到, 詳情見 2.3.5。
2.3.4 new Configuration()
之前提到過, 配置文件解析的本質就是獲得Configuration
對象。
現在, Configuration
對象在解析的過程中第一次出現了。
那我們就可以返回這個對象了?
當然不是, 這個對象現在只是創建, 后續還有很多成員變量需要根據 XML 配置文件解析后來賦值。
2.3.5 parser.parse()
這里的parser
是XMLConfigBuilder
對象。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
這個函數返回的Configuration
對象就是最終寫入SqlSessionFatory
對應成員變量的對象。
由於配置文件解析的本質就是獲得Configuration
對象, 因此, 這個函數就是解析的核心。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(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);
}
}
其對應的過程就是解析 XML 配置文件中 properties, settings, typeAliases, plugins, objectFactory, objectWrapperFactory, reflectorFactory, environments, databaseIdProvider, typeHandlers, mappers, 這些子節點。
其中的evalNode
函數, 在其函數過程中, 會調用XParhParser
中的函數, 對 xml 節點進行解析:
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
以上過程就是我們 2.3.3 中提到的第 5, 6 步過程。
具體的在后續的文章中在深入了解。
2.3.6 build(configuration)
該函數就是創建一個具體的SqlSessionFactory
對象。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
就是創建DefaultSqlSessionFactory
對象, 並將configuration
賦值給相應的成員變量。
更具體的解析配置的過程, 后續分享。
一起學 mybatis
你想不想來學習 mybatis? 學習其使用和源碼呢?那么, 在博客園關注我吧!!
我自己打算把這個源碼系列更新完畢, 同時會更新相應的注釋。快去 star 吧!!