天天看點

MyBatis初始化--詳解

一、 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等,這将在後續的文章中讨論)。如下圖所示:

MyBatis初始化--詳解

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初始化--詳解
由上圖所示,mybatis初始化要經過簡單的以下幾步:
  1. 調用SqlSessionFactoryBuilder對象的build(inputStream)方法;
  2. SqlSessionFactoryBuilder會根據輸入流inputStream等資訊建立XMLConfigBuilder對象;
  3. SqlSessionFactoryBuilder調用XMLConfigBuilder對象的parse()方法;
  4. XMLConfigBuilder對象傳回Configuration對象;
  5. SqlSessionFactoryBuilder根據Configuration對象建立一個DefaultSessionFactory對象;
  6. 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資訊的操作。如圖所示:

MyBatis初始化--詳解
  1. 之後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);
    }
  }
           
  1. 然後将這些值解析出來設定到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;
}
           
  1. 傳回Configuration對象

我們将上述的MyBatis初始化基本過程的序列圖細化

MyBatis初始化--詳解

深入了解Mybatis blog.csdn.net/luanlouis/article/details/37744073