使用mybatis功能前,我們先得創建一個 SqlSessionFactory 對象,創建過程如下。我們實例化了一個 SqlSessionFactoryBuilder對象,然後讓其加載mybatis的配置文件,構建出一個SqlSessionFactory 對象。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
1. 相關組件
介紹SqlSessionFactory 對象的構建過程前,我們先熟悉一下參與這個構建過程的相關組件。以便後面我們更好的分析源碼
1.1 Configuration
見名知意,這是 mybaits提供的一個配置類。mybatis所有的配置信息,以及某些配置的默認值都封裝在此類中。在構建SqlSessionFactory對象時,mybatis會將傳入的xml配置文件的配置信息映射到configuration配置類中。並且,configuration還爲我們的別名註冊器註冊了默認的別名。
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
......
1.2 SqlSessionFactory 和 SqlSessionFactoryBuilder
SqlSessionFactory 是一個構建SqlSession的工廠。SqlSession可以被認爲是與數據庫一次會話。SqlSessionFactory提供了非常多的重載的 openSession 方法,用來構建SqlSession,以及一個獲取配置類getConfiguration方法,用來動態的獲取配置信息或更改配置信息。其實現類DefaultSqlSessionFactory內就維護了一個final的Configuration對象。我們通過通常所說的SqlSessionFactory 就是DefaultSqlSessionFactory。
同理,SqlSessionFactoryBuilder 就是用來構建SqlSessionFactory的建造者,其中維護了許多重載的build方法,用於構建SqlSessionFactory。最後所有的build方法又指向了同一個重載的 build(Configuration config) 方法,用於最終的創建SqlSessionFactory對象。
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {......}
......
}
public class SqlSessionFactoryBuilder {
......
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.
}
}
}
......
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
1.3 XPathParser 和 XMLConfigBuilder
XPathParser 是一個mybatis內置的xml文件解析器,其中維護了一個Document對象。XPathParser 在實例化時,就會將傳入的xml文件信息轉換成一個Document對象,並提供了一系列的解析Document對象的方法。
XMLConfigBuilder 就是用來生成我們的配置類的建造者,其中維護一個XPathParser 對象,其父類BaseBuilder維護一個Configuration 對象。XMLConfigBuilder利用XPathParser 解析xml文件中的各個節點,最後將解析到的信息封裝在 Configuration 對象中。其核心方法 parse方法,返回一個 Configuration 對象。
public class XMLConfigBuilder extends BaseBuilder {
private final XPathParser parser;
......
public Configuration parse() {......}
......
}
public abstract class BaseBuilder {
protected final Configuration configuration;
......
}
public class XPathParser {
private final Document document;
......
}
2. 構建過程
通過對上面相關組件的瞭解,SqlSessionFactory構建的大致流程也就比較清晰了。
- 實例化一個 SqlSessionFactoryBuilder對象,調用其build方法,並將xml信息作爲參數傳入,獲取到SqlSessionFactory。
- SqlSessionFactoryBuilder 利用xml信息作爲參數創建了一個XmlConfigBuilder對象,並調用其parse方法生成一個Configuration對象。
- XmlConfigBuilder 的 parse 方法構建並返回了 Configuration 對象。
- SqlSessionFactoryBuilder拿到 Configuration後,實例化了一個 DefaultSqlSessionFactory返回。
不難發現,SqlSessionFactory構建過程的核心就是 Configuration 對象的構建,而這個過程的具體實現就在XmlConfigBuilder的parse方法中。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
//標記爲已解析狀態
parsed = true;
//這裏的parser就是XmlConfigBuilder維護的XPathParser對象
//這裏拿到了xml的configuration節點下的所有內容,並交給parseConfiguration方法處理
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
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方法調用了一系列的方法去解析xml配置文件中的各個節點,這裏我們重點看看解析mappers節點的 mapperElement方法。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
//遍歷 mappers節點下的子節點
for (XNode child : parent.getChildren()) {
//解析 package 子節點
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//解析<mapper resource="***.xml"/>形式的配置
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
//解析<mapper url="***.xml"/>形式的配置
else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
//解析<mapper mapperClass ="***"/>形式的配置
else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
mappers節點的解析可以分成兩個部分來看
1. 如果遍歷到package子節點,則將該包下所有的Class註冊到Configuration的mapperRegistry中。若是mapper子節點的class屬性,則將指定的class註冊到Configuration的mapperRegistry中。mapperRegistry會根據接口class的全限定名找到對應的XML配置文件進行解析。
2. 如果遍歷到mapper子節點的resource或者url屬性,就直接是實例化一個 XmlMapperBuilder對象對xml進行解析。
xml映射文件的解析過程這裏不詳細敘述。XmlMapperBuilder最終會將映射文件解析成一個MappedStatement對象,並將其註冊到Configuration對象中。
當所有的節點都解析完成後SqlSessionFactory的構建過程也就基本完成了。