1. 程式人生 > >MyBatis原始碼分析(1)-MapConfig檔案的解析

MyBatis原始碼分析(1)-MapConfig檔案的解析

1.簡述

   MyBatis是一個優秀的輕ORM框架,由最初的iBatis演化而來,可以方便的完成sql語句的輸入輸出到java物件之間的相互對映,典型的MyBatis使用的方式如下:

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
  session.close();
}

   這個mybatis-config.xml檔案是MyBatis的入口,所有的資料來源、全域性性的配置、以及SqlMap檔案的位置都在這個檔案配置,以下是一個MapConfig檔案的例子:

<?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>
  <properties resource="org/apache/ibatis/builder/mapper.properties">
    <property name="driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
  </properties>
  <settings
>
    <setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="false"/>
    <setting name="multipleResultSetsEnabled" value="true"/>
    <setting name="useColumnLabel" value="true"/>
    <setting name="useGeneratedKeys" value="false"/>
    <setting name="defaultExecutorType" value="SIMPLE"/>
    <setting name="defaultStatementTimeout" value="25"/>
  </settings>
  <typeAliases>
    <typeAlias alias="Author" type="domain.blog.Author"/>
  </typeAliases>
  <typeHandlers>
    <typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.ExampleTypeHandler"/>
  </typeHandlers>
  <objectFactory type="org.apache.ibatis.builder.ExampleObjectFactory">
    <property name="objectFactoryProperty" value="100"/>
  </objectFactory>
  <plugins>
    <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
      <property name="pluginProperty" value="100"/>
    </plugin>
  </plugins>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC">
        <property name="" value=""/>
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
  </mappers>
</configuration>

弄清楚MapConfig檔案是如何解析的,以及各個配置項對應的作用,無疑對使用和理解MyBatis框架有著極大的好處。

2. 三個類的介紹

  MapConfig檔案的解析,我認為必須先介紹一下框架中的三個類,XNode,XPathParser和BaseBuilder。

  2.1 XNode

      XNode是MyBatis框架對於XML檔案中Node類的擴充套件,它的成員變數有

public class XNode {
  private Node node;   //org.w3c.dom.Node 表示的是xml中的一個節點
  private String name; //節點的名稱
  private String body; //節點的文字內容
  private Properties attributes;   //全域性性的配置變數,在新建XNode例項物件的時候傳進來
  private Properties variables;    //節點本身的屬性變數
  private XPathParser xpathParser; //節點本身擁有的XPathParser例項,這個XPathParser是一個基於
                                        //XPath機制解析XML檔案的工具類
  .....
}
一個XNode物件是一個XML檔案中節點的抽象,它提供了一系列公共方法,使得框架的其他的程式能夠很方便的訪問節點的某個屬性,例如這個方法:
public XNode evalNode(String expression) {
    return xpathParser.evalNode(node, expression);
  }

   evalNode能夠更具XPath表示式獲取一個節點下的任意一個節點,實際上呼叫的是XNode例項自身持有的XPathParser例項物件的方法。

   2.2 XPathParser

   XPathParser是一個工具類,

public class XPathParser {
  private Document document;               //持有的Document物件
  private boolean validation;              //是否進行DTD驗證
  private EntityResolver entityResolver;   //DTD驗證的介面
  private Properties variables;            //全域性性的配置變數
  private XPath xpath;                     //XPath介面
  ...
}

對於XNode中的evalNode方法,實際上呼叫的是XPathParser中的evalNode方法,程式碼片段如下:

  public XNode evalNode(String expression) {
    return evalNode(document, expression);
  }
  public XNode evalNode(Object root, String expression) {
    Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {
      return null;
    }
    return new XNode(this, node, variables);
  }
  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);
    }
  }
可見,通過層層的呼叫,最後還是呼叫的Xpath中的evaluate方法,其中第三個引數 XPathConstants.NODE 指明瞭該方法返回的是一個Node物件,第一個引數是XPath的語法表示式,XPath的教程可以參考這裡
2.3 BaseBuilder
BaseBuilder是所有MyBatis框架中所有XML檔案解析器的基類,之後的XMLConfigBuilder、XMLMapperBuilder、MapperBuilderAssistant都是繼承了此類,此類持有的屬性有:
public abstract class BaseBuilder {
  protected final Configuration configuration;              //一切的配置資訊都會彙總到這個類中
  protected final TypeAliasRegistry typeAliasRegistry;      //用於別名的解析
  protected final TypeHandlerRegistry typeHandlerRegistry;  //用於java型別的解析
  }

其中的Configuration類,就是解析XML配置檔案,SqlMap配置檔案的歸宿。

3. XMLConfigBuilder完成的工作

完成SqlConfig解析工作主要是XMLConfigBuilder它繼承自BaseBuilder,它對外提供了很多個不同簽名的公共建構函式,以滿足不同場合的需要,但是所有的這些對外的公共建構函式,最終還是呼叫的其私有的建構函式:

  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;
  }
首先,呼叫基類的建構函式,傳入一個新的Configuration例項。第二個引數environment,指定的是XMLconfig檔案中的environments下environment節點名稱,在一個配置檔案中,可以配置多個environment節點,這樣對應的是不同的環境配置資訊,例如你可以配置兩個environment節點,一個對應生產,一個對應開發。如果這個引數為空,則用id為default的environment節點對應的環境資訊。
建立了XMLConfigBuilder例項之後,主要呼叫下面的方法,完成所有配置檔案的解析:
  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);
    }
  }
該方法對應的root節點是SqlConfig檔案的根節點,呼叫root.evelNode(String nodeName)之後,返回的是SqlConfig檔案根節點下的某一個節點,之後針對不同的節點,呼叫不同的函式,進行處理。
propertiesElement(root.evalNode("properties"));
其中root.evalNode(“properties”)實際上返回的是SqlConfig檔案下的節點,對應的XML檔案片段為
  <properties resource="org/apache/ibatis/builder/mapper.properties">
    <property name="driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
  </properties>

這個節點設定了一個配置檔案,仔細來看看拿到這個節點的XNode例項之後,propertiesElement做了一些什麼:

  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      //將節點下的子節點轉換成Properties例項,其中鍵名由節點的name決定,鍵值由節點的value指定
      Properties defaults = context.getChildrenAsProperties(); 
      //獲取節點屬性resouce的值,對應一個配置檔案的地址
      String resource = context.getStringAttribute("resource");
      //獲取節點屬性url的值,對應一個配置檔案的地址
      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) {
        //將resource指定的配置檔案的轉換成Properties例項,並且和之前的生成的Properties例項合併
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      //將configuration中已經有的Properties例項讀取出來,和之前的生成的Properties例項合併
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      //將新生成的Properties例項設定回XpathParser和Configuration例項。
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

         可見,經過這麼一段處理以後,properties節點下對應的配置檔案,和單獨的以子節點形式設定的配置資訊,都以Properties例項的形式儲存到了最終要生成的Configuration例項中,這個Properties的例項也會貫徹之後的解析過程,供其他的方法使用。至此算是完成了Properties節點的解析。 
         之後的settings、typeAliases、typeHandlers等節點的解析類似,基本上都是放到不同的私有方法中來完成的,只不過由於解析物件的不同,這些方法的複雜度也不一樣。這樣的話,當新增一種的新的節點,只需要新增一種新的私有方法。

4. 小結 
         之前看過iBatis的原始碼,iBatis對於SqlConfig檔案的解析核心是利用了私有類的回撥函式來解析的,結合《iBATIS框架原始碼剖析》我還花了一整天的時間搞明白,因為裡面的程式碼實在太複雜。MyBatis對於XML檔案的解析部分全部重新了,核心是對於XML節點的抽象XNode以及對於XPath二度進行包裝的工具類XPathParser,結合GOF中的建造者模式,做到了較清晰的程式碼結構和邏輯抽象,使得程式碼更加容易讓人理解。

PS:MyBatis的原始碼分析是我14年上半年的重要工作,這個系列的部落格儘量會保持兩週一篇的速度。

相關推薦

MyBatis原始碼分析1-MapConfig檔案解析

1.簡述    MyBatis是一個優秀的輕ORM框架,由最初的iBatis演化而來,可以方便的完成sql語句的輸入輸出到java物件之間的相互對映,典型的MyBatis使用的方式如下: String resource = "org/mybatis/example/mybatis-config.xml";

Mybatis原始碼分析1—— Mapper檔案解析

感覺CSDN對markdown的支援不夠友好,總是伴隨各種問題,很惱火! xxMapper.xml的解析主要由XMLMapperBuilder類完成,parse方法來完成解析: public void parse() { if (!configuration.isRes

Mybatis 原始碼分析2—— 引數處理

Mybatis對引數的處理是值得推敲的,不然在使用的過程中對發生的一系列錯誤直接懵逼了。 以前遇到引數繫結相關的錯誤我就是直接給加@param註解,也稀裡糊塗地解決了,但是後來遇到了一些問題推翻了我的假設:單個引數不需要使用 @param 。由此產生了一個疑問,Mybatis到底是怎

Mybatis 原始碼分析9—— 事物管理

Mybatis 提供了事物的頂層介面: public interface Transaction { /** * Retrieve inner database connection * @return DataBase connection * @throw

Mybatis 原始碼分析8—— 一二級快取

一級快取 其實關於 Mybatis 的一級快取是比較抽象的,並沒有什麼特別的配置,都是在程式碼中體現出來的。 當呼叫 Configuration 的 newExecutor 方法來建立 executor: public Executor newExecutor(Transac

Mybatis原始碼分析7—— 結果集處理

解析封裝 ResultMap 是和結果集相關的東西,最初在解析 XML 的時候,於 parseStatementNode 方法中,針對每一個 select 節點進行解析,轉換為 MappedStatement(類似 Spring 的 bean 配置和 BeanDefinition 的

Mybatis原始碼分析6—— 從JDBC看Mybatis的設計

Java資料庫連線,(Java Database Connectivity,簡稱JDBC)是Java語言中用來規範客戶端程式如何來訪問資料庫的應用程式介面,提供了諸如查詢和更新資料庫中資料的方法。 六步流程: 載入驅動(5.x驅動包不需要這步了) 建立

Mybatis原始碼分析5—— 外掛的原理

MyBatis 允許你在已對映語句執行過程中的某一點進行攔截呼叫。 預設情況下,可以使用外掛來攔截的方法呼叫包括: Executor (update, query, flushStatements, commit, rollback, getTransaction, cl

Mybatis原始碼分析4—— Mapper的建立和獲取

Mybatis我們一般都是和Spring一起使用的,它們是怎麼融合到一起的,又各自發揮了什麼作用? 就拿這個Mapper來說,我們定義了一個介面,聲明瞭一個方法,然後對應的xml寫了這個sql語句, 它怎麼就執行成功了?這傢伙是怎麼實現的,帶著這個好奇心,我一步步跟蹤,慢慢揭開了它的

Mybatis原始碼分析3—— 從Mybatis的視角去看Bean的初始化流程

不涉及Spring完整的啟動流程,僅僅從Mybatis的視角去分析幾個關鍵的方法,找到Mybatis是如何通過這幾個擴充套件點植入進去的,反過來看Spring是如何設計,埋下這些伏筆,實現其可擴充套件性。 springContext-mybatis.xml的配置: <!--

比特幣BTC原始碼分析1:地址生成過程

一、生成一個比特幣錢地址 二、根據原始碼整理比特幣地址生成過程 1、取得公鑰PubKey 2、使用 RIPEMD160(SHA256(PubKey)) 雜湊演算法,取公鑰並對其雜湊兩次 3、給雜湊加上地址生成演算法版本的字首 4、對於第二步生成的結果,使用SHA256(SHA256

以太坊ETH原始碼分析1:地址生成過程

一、生成一個以太坊錢包地址 通過以太坊命令列客戶端geth可以很簡單的獲得一個以太坊地址,如下: ~/go/src/github.com/ethereum/go-ethereum/build/bin$geth account new INFO [11-03|20:09:33.219]

jdk原始碼分析1java.lang.Object

java.lang.Object原始碼分析 public final native Class<?> getClass() public native int hashCode(); public boolean e

tensorflow原始碼分析1

variable類:        通過例項化Variable類可以新增一個變數到graph,在使用變數之前必須對變數顯示的初始化,初始化可以使用assign為變數賦值也可以通過變數本身的initializer方法。     &nb

ES5.6.2原始碼分析1:準備工作

1、gradle安裝 下載4.5版本,解壓後配置環境變數即可。 注:gradle安裝完成後, 為了加快依賴檔案的下載需要在使用者目錄中新建init.gradle檔案(讓全域性可見,build時會用到)。檔案的具體內容為: 目錄:C:\Users\admin.gradle

tensorflowV1.11-原始碼分析1

##</Users/deepmyhaspl/docs/tensorflow-src/tensorflow-r1.11>####[4]|<====configure.py=====>|## # Copyright 2017 The TensorFlow Authors. All

Django rest framework原始碼分析1----認證

目錄 一、基礎 1.1.安裝 兩種方式: pip install djangorestframework 1.2.需要先了解的一些知識 理解下面兩個知識點非常重要,django-rest-framework原始碼中到處都是基於CBV和麵向物件的封裝 (1)面向物件封裝的兩大特性

Android6.0的Looper原始碼分析1

Android在Java標準執行緒模型的基礎上,提供了訊息驅動機制,用於多執行緒之間的通訊。而其具體實現就是Looper。 Android Looper的實現主要包括了3個概念:Message,MessageQueue,Handler,Looper。其中Message就是

libevent原始碼分析1

有過看nginx原始碼的基礎,現在來看libevent原始碼,感覺要輕鬆多了。。 第一篇文章,主要是還是介紹一些幾個重要的資料結構吧。。。。 首先是event結構:struct event { TAILQ_ENTRY (event) ev_next; //用於構成eve

Freescale i.MX6 Linux Ethernet Driver驅動原始碼分析1

最近需要在Freescale i.MX6上移植Ethernet AVB的核心patch,Ethernet AVB的Wiki:http://en.wikipedia.org/wiki/Audio_Video_Bridging,而Freescale原來已經在kernel 3.