一、 MyBatis的初始化做了什麼
任何架構的初始化,無非是加載自己運作時所需要的配置資訊。
MyBatis的配置資訊,大概包含以下資訊,其高層級結構如下:
<configuration> 配置
<properties> 屬性
<settings> 設定
<typeAliases> 類型命名
<typeHandlers> 類型處理器
<objectFactory> 對象工廠
<plugins> 插件
<environments> 環境
<environment> 環境變量
<transactionManager> 事務管理器
<dataSource> 資料源
MyBatis的上述配置資訊會配置在XML配置檔案中(mybatis-config.xml),那麼,這些資訊被加載進入MyBatis内部,MyBatis是怎樣維護的呢?
MyBatis采用了一個非常直白和簡單的方式---使用 org.apache.ibatis.session.Configuration 對象作為一個所有配置資訊的容器,Configuration對象的組織結構和XML配置檔案的組織結構幾乎完全一樣(當然,Configuration對象的功能并不限于此,它還負責建立一些MyBatis内部使用的對象,如Executor等,這将在後續的文章中讨論)。如下圖所示:

image.png
MyBatis根據初始化好Configuration資訊,這時候使用者就可以使用MyBatis進行資料庫操作了。
可以這麼說,MyBatis初始化的過程,就是建立 Configuration對象的過程。
MyBatis的初始化可以有兩種方式:
- 基于XML配置檔案:基于XML配置檔案的方式是将MyBatis的所有配置資訊放在XML檔案中,MyBatis通過加載并XML配置檔案,将配置文資訊組裝成内部的Configuration對象
- 基于Java API:這種方式不使用XML配置檔案,需要MyBatis使用者在Java代碼中,手動建立Configuration對象,然後将配置參數set 進入Configuration對象中
PS: MyBatis具體配置資訊有哪些,又分别表示什麼意思,不在本文的叙述範圍,可以參考《Java Persistence withMyBatis 3 (中文版)》 的第二章 引導MyBatis中有詳細的描述)
接下來我們将通過 基于XML配置檔案方式的MyBatis初始化,深入探讨MyBatis是如何通過配置檔案建構Configuration對象,并使用它的。
MyBatis基于XML配置檔案建立Configuration對象的過程
現在就從使用MyBatis的簡單例子入手,深入分析一下MyBatis是怎樣完成初始化的,都初始化了什麼。看以下代碼:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");
有過MyBatis使用經驗的讀者會知道,上述語句的作用是執行com.foo.bean.BlogMapper.queryAllBlogInfo 定義的SQL語句,傳回一個List結果集。總的來說,上述代碼經曆了mybatis初始化 -->建立SqlSession -->執行SQL語句 傳回結果三個過程。
上述代碼的功能是根據配置檔案mybatis-config.xml 配置檔案,建立SqlSessionFactory對象,然後産生SqlSession,執行SQL語句。而mybatis的初始化就發生在第三句:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 現在就讓我們看看第三句到底發生了什麼。
MyBatis初始化基本過程
SqlSessionFactoryBuilder根據傳入的資料流生成Configuration對象,然後根據Configuration對象建立預設的SqlSessionFactory執行個體。
初始化的基本過程如下序列圖所示:
由上圖所示,mybatis初始化要經過簡單的以下幾步:
- 調用SqlSessionFactoryBuilder對象的build(inputStream)方法;
- SqlSessionFactoryBuilder會根據輸入流inputStream等資訊建立XMLConfigBuilder對象;
- SqlSessionFactoryBuilder調用XMLConfigBuilder對象的parse()方法;
- XMLConfigBuilder對象傳回Configuration對象;
- SqlSessionFactoryBuilder根據Configuration對象建立一個DefaultSessionFactory對象;
- SqlSessionFactoryBuilder傳回 DefaultSessionFactory對象給Client,供Client使用。
SqlSessionFactoryBuilder相關的代碼如下所示
public SqlSessionFactory build(InputStream inputStream)
{
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)
{
try
{
//2. 建立XMLConfigBuilder對象用來解析XML配置檔案,生成Configuration對象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//3. 将XML配置檔案内的資訊解析成Java對象Configuration對象
Configuration config = parser.parse();
//4. 根據Configuration對象建立出SqlSessionFactory對象
return build(config);
}
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.
}
}
}
//從此處可以看出,MyBatis内部通過Configuration對象來建立SqlSessionFactory,使用者也可以自己通過API構造好Configuration對象,調用此方法建立SqlSessionFactory
public SqlSessionFactory build(Configuration config)
{
return new DefaultSqlSessionFactory(config);
}
上述的初始化過程中,涉及到了以下幾個對象:
- SqlSessionFactoryBuilder : SqlSessionFactory的構造器,用于建立SqlSessionFactory,采用了Builder設計模式
- Configuration :該對象是mybatis-config.xml檔案中所有mybatis配置資訊
- SqlSessionFactory:SqlSession工廠類,以工廠形式建立SqlSession對象,采用了Factory工廠設計模式
- XmlConfigParser :負責将mybatis-config.xml配置檔案解析成Configuration對象,共SqlSessonFactoryBuilder使用,建立SqlSessionFactory
建立Configuration對象的過程
接着上述的 MyBatis初始化基本過程讨論,當SqlSessionFactoryBuilder執行build()方法,調用了XMLConfigBuilder的parse()方法,然後傳回了Configuration對象。那麼parse()方法是如何處理XML檔案,生成Configuration對象的呢?
1. XMLConfigBuilder會将XML配置檔案的資訊轉換為Document對象,而XML配置定義檔案DTD轉換成XMLMapperEntityResolver對象,然後将二者封裝到XpathParser對象中,XpathParser的作用是提供根據Xpath表達式擷取基本的DOM節點Node資訊的操作。如圖所示:
- 之後XMLConfigBuilder調用parse()方法:會從XPathParser中取出 <configuration>節點對應的Node對象,然後解析此Node節點的子Node:properties, settings, typeAliases,typeHandlers, objectFactory, objectWrapperFactory, plugins, environments,databaseIdProvider, mappers
public Configuration parse()
{
if (parsed)
{
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//源碼中沒有這一句,隻有 parseConfiguration(parser.evalNode("/configuration"));
//為了讓讀者看得更明晰,源碼拆分為以下兩句
XNode configurationNode = parser.evalNode("/configuration");
parseConfiguration(configurationNode);
return configuration;
}
/*
解析 "/configuration"節點下的子節點資訊,然後将解析的結果設定到Configuration對象中
*/
private void parseConfiguration(XNode root) {
try {
//1.首先處理properties 節點
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
//2.處理typeAliases
typeAliasesElement(root.evalNode("typeAliases"));
//3.處理插件
pluginElement(root.evalNode("plugins"));
//4.處理objectFactory
objectFactoryElement(root.evalNode("objectFactory"));
//5.objectWrapperFactory
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.settings
settingsElement(root.evalNode("settings"));
//7.處理environments
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
//8.database
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9. typeHandlers
typeHandlerElement(root.evalNode("typeHandlers"));
//10 mappers
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
- 然後将這些值解析出來設定到Configuration對象中。
解析子節點的過程這裡就不一一介紹了,使用者可以參照MyBatis源碼仔細揣摩,我們就看上述的environmentsElement(root.evalNode("environments")); 方法是如何将environments的資訊解析出來,設定到Configuration對象中的:
/*
解析environments節點,并将結果設定到Configuration對象中
注意:建立envronment時,如果SqlSessionFactoryBuilder指定了特定的環境(即資料源);
則傳回指定環境(資料源)的Environment對象,否則傳回預設的Environment對象;
這種方式實作了MyBatis可以連接配接多資料源
*/
private void environmentsElement(XNode context) throws Exception
{
if (context != null)
{
if (environment == null)
{
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren())
{
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id))
{
//1.建立事務工廠 TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//2.建立資料源DataSource
DataSource dataSource = dsFactory.getDataSource();
//3. 構造Environment對象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//4. 将建立的Envronment對象設定到configuration 對象中
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
private boolean isSpecifiedEnvironment(String id)
{
if (environment == null)
{
throw new BuilderException("No environment specified.");
}
else if (id == null)
{
throw new BuilderException("Environment requires an id attribute.");
}
else if (environment.equals(id))
{
return true;
}
return false;
}
- 傳回Configuration對象
我們将上述的MyBatis初始化基本過程的序列圖細化
深入了解Mybatis blog.csdn.net/luanlouis/article/details/37744073