MyBatis架構設計及源代碼分析(一):MyBatis架構

如果不太熟悉MyBatis使用的請先參見MyBatis官方文檔,這對理解其架構設計和源碼分析有很大好處。

一、概述

MyBatis並不是一個完整的ORM框架,其官方首頁是這麼介紹自己

The MyBatis data mapper framework makes it easier to use a relational database with object-oriented applications. MyBatis couples objects with stored procedures or SQL statements using a XML descriptor or annotations. Simplicity is the biggest advantage of the MyBatis data mapper over object relational mapping tools.
 

而在其官方文檔中介紹“What is MyBaits”中說到

MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records. 
 
ORM是Object和Relation之間的映射,包括Object->Relation和Relation->Object兩方面。Hibernate是個完整的ORM框架,而MyBatis完成的是Relation->Object,也就是其所說的data mapper framework。關於ORM的一些設計思路和細節可以參見Martin Flow《企業應用架構模式》一書中的ORM章節,MyBatis並不刻意於完成ORM(對象映射)的完整概念,而是旨在更簡單、更方便地完成數據庫操作功能,減輕開發人員的工作量,我想這對於應用系統來說也是最實用的,相信用Hibernate的都受過它的痛苦,而用過MyBatis的都會感覺它很簡捷輕鬆。
 

二、整體架構

下面是從功能流程層次描述MyBatis的整體架構圖

image

 

而下面是MyBatis源碼包對應的架構圖

image

 

下面以“功能流程角度的架構圖”來簡要地分析下各層的架構,在後面系列文章中將有專題來深入解析MyBatis重要的功能點。

 

1、接口層

我們知道,在不考慮與Spring集成的情況下,使用MyBatis執行數據庫操作的代碼如下
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
  Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
  session.close();
}

SqlSessionFactory、SqlSession這是MyBatis接口層的核心類,尤其是SqlSession,是實現所有數據庫操作的API,這幾個類都是org.apache.ibatis.session包下的,這個包的主體類結構圖如下

image

Configuration是MyBatis中相當重要的一個類,可以這麼說,如果理解了其中的所有參數的意義,不僅清楚地知道MyBatis提供的所有配置項,還理解了MyBatis的內部核心運行原理,當然要真正理解這些參數的意義及實現,還需要閱讀完完整的MyBatis框架之後才能做到。

由上圖可以看到,Configuration對象與DefaultSqlSessionFactory是1:1的關聯關係,這也就意味着在一個DefaultSqlSessionFactory衍生出來的所有SqlSession作用域裏,Configuration對象是全局唯一的。同時SqlSessionFactory提供了getConfiguration()接口來公開Configuration對象,因此開發者除了配置文件之外,還可以在程序裏動態更改Configuration的屬性項以達到動態調整的目的,但此時不僅要考慮到執行完reset,同時還要考慮在修改過程中會可能影響到其他SqlSession的執行。

2、核心層

2.1 配置解析

在應用啓動的時候,MyBatis解析兩種配置文件

  • SqlMapConfig.xml
  • SqlMap.xml

SqlMapConfig.xml是在XMLConfigBuilder類中完成解析的,其類圖關係大致如下

1

我們知道XML有兩種解析方式:一是DOM,另一個是SAX,MyBatis使用的是org.wrc.dom——JDK提供的文檔對象模型(DOM)接口(SqlMapConfig.xml並不大,所以DOM方式並沒有什麼效率損耗,JDK也提供了SAX模型接口org.xml.sax,這兩個都是JAXP的組件API),以及JDK官方提供的javax.xml.xpath.XPath來作爲XML路徑尋找組件。

SqlMap.xml是在XMLMapperBuilder中解析完成的,其中把對Statement的解析(即SqlMap.xml中SELECT|INSERT|UPDATE|DELETE定義部分)委託給XMLStatementBuilder來完成。SqlMap.xml的解析比較複雜的,涉及到PreparedMapping、ResultMapping、LanguageDriver、Discriminator、緩存、自動映射等一系列對象的構造,這裏暫時略過,後面專題分析。

2.2 SQL執行

MyBatis中Executor是的核心,圍繞着它完成了數據庫操作的完整過程。下面是Executor的類圖

image

在上圖中我列出了Executor中方法的參數,而在其子類中就沒有明確寫出。從上圖中可以看到,Executor主要提供了

  • QUERY|UPDATE(INSERT和DELETE也是使用UPDATE),從方法定義中可看到,它需要MappedStatement、parameter、resultHandler這幾個實例對象,這幾個也是SQL執行的主要部分,詳細實現在後面專題中再介紹。
  • 事務提交/回滾,這委託給Transaction對象來完成。
  • 緩存,createCacheKey()/isCached()。
  • 延遲加載,deferload()。
  • 關閉,close(),主要是事務回滾/關閉。

BaseExecutor的屬性表明:它內部維護了localCache來localOutputParameterCache來處理緩存,至於這緩存保存的是什麼,這後面專題再說。以及線程安全的延遲加載列表deferredLoads、事務對象Transaction。

BatchExecutor的屬性已經表明:它內部維護了StatementList批量提交併通過batchResultList保存執行結果。

ResueExecutor的屬性及方法表明:它內部維護了java.sql.Statement對象緩存,以重用Statement對象(對於支持預編譯的數據庫而言,在創建PreparedStatement時需要發送一次數據庫請求預編譯,而重用Statement對象主要是減少了這次預編譯的網路開銷)。

下面以SqlSession.selectList爲例,畫出SQL執行的時序圖(點擊下方的圖片查看大圖,部分分支有所簡化)

未命名

 

3、基礎層

3.1、logging:

MyBatis使用了自己定義的一套logging接口,根據開發者常使用的日誌框架——Log4j、Log4j2、Apache Commons Log、java.util.logging、slf4j、stdout(控制檯)——分別提供了適配器。由於各日誌框架的Log級別分類法有所不同(比如java.util.logging.Level提供的是All、FINEST、FINER、FINE、CONFIG、INFO、WARNING、SEVERE、OFF這九個級別,與通常的日誌框架分類法不太一樣),MyBatis統一提供trace、debug、warn、error四個級別,這基本與主流框架分類法是一致的(相比而言缺少Info,也許MyBatis認爲自己的日誌要麼是debug需要的,要麼就至少是warn,沒有Info的必要)。

在org.apache.ibatis.logging裏還有個比較特殊的包jdbc,這不是按字面意義理解把日誌通過jdbc記錄到數據庫裏,而是將jdbc操作以開發者配置的日誌框架打印出來,這也就是我們在開發階段常用的跟蹤SQL語句、傳入參數、影響行數這些重要的調試信息。

3.2、IO

MyBatis裏的IO主要是包含兩大功能:提供讀取資源文件的API、封裝MyBatis自身所需要的ClassLoader和加載順序。

3.3、reflection

在MyBatis如參數處理、結果映射這些大量地使用了反射,需要頻繁地讀取Class元數據、反射調用get/set,因此MyBatis提供了org.apache.ibatis.reflection對常見的反射操作進一步封裝,以提供更簡潔方便的API。比如我們reflect時總是要處理異常(IllegalAccessException、NoSuchMethodException),MyBatis統一處理爲自定義的RuntimeException,減少代碼量。

3.4、exceptions

在以Spring爲代表的開源框架中,對於應用程序中無法進一步處理的異常大都轉成RuntimeException來方便調用者操作,另外如頻繁遇到的SQLException,JDK約定其是個Exception,從JDK的角度考慮,強制要求開發者捕獲SQLException是爲了能在catch/finally中關閉數據庫連接,而Spring之類的框架爲開發者做了資源管理的事情,自然就不需要開發者再煩心SQLException,因此封裝轉換成RuntimeException。MyBatis的異常體系不復雜,org.apache.ibatis.exceptions下就幾個類,主要被使用的是PersistenceException。

3.5、緩存

緩存是MyBatis裏比較重要的部分,有兩種緩存:

  • SESSION或STATEMENT作用域級別的緩存,默認是SESSION,BaseExecutor中根據MappedStatement的Id、SQL、參數值以及rowBound(邊界)來構造CacheKey,並使用BaseExccutor中的localCache來維護此緩存。
  • 全局的二級緩存,通過CacheExecutor來實現,其委託TransactionalCacheManager來保存/獲取緩存,這個全局二級緩存比較複雜,後面還需要專題分析,至於其緩存的效率以及應用場景也留到那時候再分析。

3.6、數據源/連接池

MyBatis自身提供了一個簡易的數據源/連接池,在org.apache.ibatis.datasource下,後面專題分析。主要實現類是PooledDataSource,包含了最大活動連接數、最大空閒連接數、最長取出時間(避免某個線程過度佔用)、連接不夠時的等待時間,雖然簡單,卻也體現了連接池的一般原理。阿里有個“druid”項目,據他們說比proxool、c3p0的效率還要高,可以學習一下。

3.7 事務

MyBatis對事務的處理相對簡單,TransactionIsolationLevel中定義了幾種隔離級別,並不支持內嵌事務這樣較複雜的場景,同時由於其是持久層的緣故,所以真正在應用開發中會委託Spring來處理事務實現真正的與開發者隔離。分析事務的實現是個入口,藉此可以瞭解不掃JDBC規範方面的事情。

 

後續將對MyBatis各個部分做詳細的設計及源代碼分析,由於讀取和解析SqlMapConfig.xml和SqlMap.xml的邏輯與各個模塊的相關性較強,因此將把這部分內容與在各模塊組合在一起分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章