Mybatis3源碼分析(2)體系結構與緩存

工作流程分析

  • 首先在 MyBatis 啓動的時候我們要去解析配置文件,包括全局配置文件和映射器配置文件,這裏麪包含了我們怎麼控制 MyBatis 的行爲,和我們要對數據庫下達的指令,也就是我們的 SQL 信息。我們會把它們解析成一個 Configuration 對象。
  • 接下來就是我們操作數據庫的接口,它在應用程序和數據庫中間,代表我們跟數據庫之間的一次連接:這個就是 SqlSession 對象。
  • 我們要獲得一個會話,必須有一個會話工廠SqlSessionFactory。SqlSessionFactory 裏面又必須包含我們的所有的配置信息,所以我們會通過一個Builder 來創建工廠類。
  • 我們知道,MyBatis 是對 JDBC 的封裝,也就是意味着底層一定會出現 JDBC 的一些核心對象,比如執行 SQL 的 Statement,結果集 ResultSet。在 Mybatis 裏面,SqlSession 只是提供給應用的一個接口,還不是 SQL 的真正的執行對象。
  • 我們上次課提到了,SqlSession 持有了一個 Executor 對象,用來封裝對數據庫的操作。在執行器 Executor 執行 query 或者 update 操作的時候我們創建一系列的對象,來處理參數、執行 SQL、處理結果集,這裏我們把它簡化成一個對象:StatementHandler,
    在閱讀源碼的時候我們再去了解還有什麼其他的對象。

流程圖

MyBatis 架構分層與模塊劃分

跟Spring 一樣,MyBatis 按照功能職責的不同,所有的 package可以分成不同的工作層次。
我們可以把 MyBatis 的工作流程類比成餐廳的服務流程。
第一個是跟客戶打交道的服務員,它是用來接收程序的工作指令的,我們把它叫做接口層。
第二個是後臺的廚師,他們根據客戶的點菜單,把原材料加工成成品,然後傳到窗口。這一層是真正去操作數據的,我們把它叫做核心層。
最後就是餐廳也需要有人做後勤(比如清潔、採購、財務),來支持廚師的工作和整個餐廳的運營。我們把它叫做基礎層。

接口層

首先接口層是我們打交道最多的。核心對象是 SqlSession,它是上層應用和 MyBatis打交道的橋樑,SqlSession 上定義了非常多的對數據庫的操作方法。接口層在接收到調用請求的時候,會調用核心處理層的相應模塊來完成具體的數據庫操作。

核心處理層

接下來是核心處理層。既然叫核心處理層,也就是跟數據庫操作相關的動作都是在這一層完成的。核心處理層主要做了這幾件事:

  1. 把接口中傳入的參數解析並且映射成 JDBC 類型;
  2. 解析 xml 文件中的 SQL 語句,包括插入參數,和動態 SQL 的生成;
  3. 執行 SQL 語句;
  4. 處理結果集,並映射成 Java 對象

插件也屬於核心層,這是由它的工作方式和攔截的對象決定的。

基礎支持層

最後一個就是基礎支持層。基礎支持層主要是一些抽取出來的通用的功能(實現複用),用來支持核心處理層的功能。比如數據源、緩存、日誌、xml 解析、反射、IO、事務等等這些功能。

MyBatis 緩存詳解

從上圖中我們可以看出Cache的默認實現只有PerpetualCache;不過mybatis利用了裝飾器模式來擴展的緩存接口:上面包decorators下面的東西就是裝飾器。我們也可以自定義緩存類,比如用redis來實現Cache這個類也是可以的,但是都是必須實現Cache這個接口。

所有的緩存實現類總體上可分爲三類:基本緩存、淘汰算法緩存、裝飾器緩存。

參數的話我一般是使用註解@CacheNamespace到mapper上面也可以配置在Cache標籤裏面

{% raw %}

緩存實現類 描述 作用 裝飾條件
基本緩存 緩存基本實現類 默認是 PerpetualCache,也可以自定義比如RedisCache、EhCache 等,具備基本功能的緩存類
LruCache LRU 策略的緩存 當緩存到達上限時候,刪除最近最少使用的緩存(Least Recently Use) eviction="LRU"(默認)
FifoCache FIFO 策略的緩存 當緩存到達上限時候,刪除最先入隊的緩存 eviction="FIFO"
SoftCache、WeakCache 帶清理策略的緩存 通過 JVM 的軟引用和弱引用來實現緩存,當 JVM內存不足時,會自動清理掉這些緩存,基於SoftReference 和 WeakReference eviction="SOFT" eviction="WEAK"
LoggingCache 帶日誌功能的緩存 比如:輸出緩存命中率 基本
SynchronizedCache 同步緩存 基於 synchronized 關鍵字實現,解決併發問題 基本
BlockingCache 阻塞緩存 通過在 get/put 方式中加鎖,保證只有一個線程操作緩存,基於 Java 重入鎖實現 blocking=true
SerializedCache 支持序列化的緩存 將對象序列化以後存到緩存中,取出時反序列化 readOnly=false(默認)
ScheduledCache 定時調度的緩存 在進行 get/put/remove/getSize 等操作前,判斷緩存時間是否超過了設置的最長緩存時間(默認是一小時),如果是則清空緩存--即每隔一段時間清空一次緩存 flushInterval 不爲空
TransactionalCache 事務緩存 在二級緩存中使用,可一次存入多個緩存,移除多個緩存 在TransactionalCacheManager 中用 Map維護對應關係
{% endraw %}

可以看出緩存是用map存儲的

一級緩存

一級緩存也叫本地緩存,MyBatis的一級緩存是在會話(SqlSession)層面進行緩存的。MyBatis 的一級緩存是默認開啓的,不需要任何的配置。
禁用緩存可以使用@Options註解也可以使用在mapper.xml文件裏面的flushCache

  1. 我們知道,緩存最終會放到PerpetualCache中。
  2. sqlSession默認實現是defaultSession,然而defaultSession裏面有個配置文件和執行器,那麼執行器又是BaseExecutor維護,緩存就應該在BaseExecutor裏面
一級緩存驗證

執行更新操作後緩存會失效

其他會話更新了數據,導致讀取到髒數據(一級緩存不能跨會話共享)

二級緩存

二級緩存是Namespace級別的,也就是mapper級別的查詢會緩存,增刪改會刷新緩存,所以開啓是針對mapper配置就行了。我一般用@CacheNamespace或者@CacheNamespaceRef(代表引用別的命名空間的Cache配置,兩個命名空間的操作使用的是同一個Cache,這個可以解決連表查詢導致緩存失效,但是緩存粒度就變大了,刷新緩存會影響多個mapper)

二級緩存是用來解決一級緩存不能跨會話共享的問題的,可以被多個 SqlSession 共享(只要是同一個接口裏面的相同方法,都可以共享),生命週期和應用同步。

二級緩存是工作在一級緩存之前的。實際上 MyBatis 用了一個裝飾器的類來維護,就是 CachingExecutor。如果啓用了二級緩存,MyBatis 在創建 Executor 對象的時候會對 Executor 進行裝飾。CachingExecutor 對於查詢請求,會判斷二級緩存是否有緩存結果,如果有就直接返回,如果沒有委派交給真正的查詢器 Executor 實現類,比如 SimpleExecutor 來執行查詢,再走到一級緩存的流程。最後會把結果緩存起來,並且返回給用戶。

二級緩存驗證


可以看到已經開啓了二級緩存了,共享了sqlsession了。從上圖中,我們可以看出,緩存是通過用戶提交後才緩存起來的。
MyBatis二級緩存的工作流程和前文提到的一級緩存類似,只是在一級緩存處理前,用CachingExecutor裝飾了BaseExecutor的子類,實現了二級緩存的查詢和寫入功能。

通過執行器進入CachingExecutor打的debug(可以看出一層層裝飾最後纔到基本實現類)

緩存是通過TransactionalCacheManager存儲的


存儲TransactionalCache這個對象 這裏就已經說明了,可以執行多個操作然後在提交緩存。支持多個緩存的添加與刪除。

總結

一級緩存
  1. MyBatis一級緩存的生命週期和SqlSession一致。
  2. MyBatis一級緩存內部設計簡單,只是一個沒有容量限定的HashMap,在緩存的功能性上有所欠缺。
  3. MyBatis的一級緩存最大範圍是SqlSession內部,有多個SqlSession或者分佈式的環境下,數據庫寫操作會引起髒數據,建議設定緩存級別爲Statement,這樣也就失去了緩存的意義。
二級緩存
  1. MyBatis的二級緩存相對於一級緩存來說,實現了SqlSession之間緩存數據的共享,同時粒度更加的細,能夠到namespace級別,通過Cache接口實現類不同的組合,對Cache的可控性也更強。
  2. MyBatis在多表查詢時,極大可能會出現髒數據,有設計上的缺陷,安全使用二級緩存的條件比較苛刻。
  3. 在分佈式環境下,由於默認的MyBatis Cache實現都是基於本地的,分佈式環境下必然會出現讀取到髒數據,需要使用集中式緩存將MyBatis的Cache接口實現,有一定的開發成本而mybatis官網也提供了很多第三方緩存的方案,但是也是比較雞肋的。
  4. 直接使用Redis、Memcached等分佈式緩存可能成本更低,安全性也更高。實際上,一般開發中,要用真正的緩存來提高效率,目前還是單獨的第三方緩存方案比較可行。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章