接上次聊到了mybatis數據源組件後,今天咱們來看看你們平時可能都用了的緩存組件。其實緩存組件在咱們項目中要謹慎地選擇使用,用很差不只不會帶來性能上的提高,反而會出現數據的錯亂問題,爲何呢?那莫過於從mybatis緩存組件源碼中來找到答案了。算法
總體設計架構
sql
緩存的概況編程
1.首先mybatis的緩存分爲一級緩存和二級緩存,一級緩存默認是開啓的(你能夠再mapper.xml中select標籤加入flushCache="true"將它關閉);一級緩存的生命週期是SqlSession級別,它是線程安全的方式;同一個會話查詢時,mybatis會把執行的方法和參數經過算法生成key, 將鍵值和查詢結果存入map對象中。若是查詢的方法和參數徹底一致,那麼算法會生成相同key, 這時若緩存中存在則直接返回緩存中的結果。設計模式
2.二級緩存須要開啓的點有兩個地方:第一個地方: 在mybatis-config.xml文件中的setting標籤中設置, cacheEnabled="true"(mybatis全局開關,默認開啓); 第二地方:在須要開啓緩存的mapper接口的xml文件中加入<cache></cache>或<cache-ref/>標籤,固然裏面有一些屬性你能夠經過它們擴展cache的一些能力;緩存
映射語句文件中的全部 select 語句將會被緩存。安全
eviction: 緩存會使用 Least Recently Used(LRU,最近最少使用的)算法來收回。mybatis
flushInterval: 根據時間表(好比 no Flush Interval,沒有刷新間隔), 緩存不會以任什麼時候間順序 來刷新。架構
size: 緩存會存儲列表集合或對象(不管查詢方法返回什麼)的 512個引用。併發
readOnly: 緩存會被視爲是 read/write(可讀/可寫)的緩存;app
3.不論是一級緩存仍是二級緩存,當有INSERT,UPDATE,DELETE語句執行時,都會自動刷新相關緩存;
4.二級緩存的生命週期爲應用程序級,二級緩存的緩存單位時namespace,不一樣namespace之間容許共享同一緩存;
咱們今天分析mybatis緩存組件要解決如下三個問題:
1.二級緩存的總體架構、關係以及設計思路;
2. 這種設計的好處 ?
3. 分析一些比較有表明性的部件源碼;
4. 緩存能力在整個mybatis中的集成;
先來看看mybatis緩存組件的核心設計結構
核心設計結構
以上設計結構中:
PerpetualCache: 爲緩存提供默認實現(基礎的二級緩存能力) ;
其它均爲Cache的各類裝飾器
SynchronizedCache: 同步裝飾器,賦予Cache同步、線程安全的二級緩存的能力;
SerializedCache:序列化裝飾器,賦予Cache數據二進制流序列化能力;
LoggingCache:緩存日誌裝飾器,賦予Cache日誌打印能力;
LruCache:清空緩存裝飾器,賦予Cache清理淘汰策略(默認LRU, 最近最少使用清空策略);
以上四個裝飾器是mybatis二級緩存默認自帶的各類能力配置。咱們經過修改<cache/>標籤的各個屬性,可修改定義咱們的緩存須要具有的能力和特性。這裏咱們重點來分析一個很是有意思的緩存裝飾器:BlockingCache 阻塞式裝飾器,在分析這個裝飾器前,先來講說組件涉及到的裝飾器設計模式。
裝飾器設計模式
裝飾器設計模式做用是:容許動態向現有對象添加新功能;是一種代替繼承的技術。當前對象無需經過繼承擴展父類的功能,相比於繼承它更加靈活且避免了子類的快速增長。
Component: 組件做爲裝飾器的核心接口,它定義了裝飾器須要實現的行爲;
ConcreteComponent:組件實現類,實現了Component所需的行爲的基本實現,後面各個具體的裝飾器都基於該對象進行擴展;
Decorator: 裝飾器的基類(須要時可附加此類),抽象多個裝飾器在實現過程裝的一些公共行爲和基礎特性;
ConcreteDecorator:具體的裝飾器類,它繼承於裝飾器基類,實現某一個特性的裝飾器行爲;
裝飾器設計模式的優勢
類的繼承和代理,從本質上講只能算一種靜態的設計。在設計時要具體地知道對象或類須要擴展哪一個特性,較少的類沒問題,但當
1.基礎類須要擴展的特性不少;
2.擴展的這些特性設計人員並清楚怎樣裝配,而須要開放給使用它的用戶個性化的組合;
遇到以上兩個需求的時候,繼承和代理就顯得力不從心,要麼須要增長繁雜的子類;要麼須要修改原有加強功能的業務代碼,這些都違背了單一職責和開閉原則。
因此mybatis在二級緩存的加強特性須要用戶的個性化配置、添加和切換的設計目標下,選擇了裝飾器模式。
OK這裏都明白後,咱們接下來分析一下BlockingCache阻塞式緩存特性。
BlockingCache阻塞式緩存
直接上核心的實現源碼,咱們來看看它的實現細節
從以上源碼咱們不難發現:此裝飾器在提供線程安全的緩存讀寫的同時,又考慮到了拿鎖的開銷。因此採用了粒度較小的分片鎖機制,只針對特定的緩存key加鎖。這樣作至少有兩個好處:
1.提升了緩存讀寫的效率,減少了阻塞的粒度;
2.在高併發時,這種對key加鎖的機制能夠有效解決緩存擊穿的問題;
OK,這裏get到了後,下面咱們就來繼續分享一下mybatis緩存的小點CacheKe類
緩存鍵CacheKey
由上截圖,緩存的接口API咱們能夠得知,mybatis存取緩存的key是一個Object類型,這個Object實際傳入的參數就是CacheKey對象
那Cachekey對象中會添加設置那些參數呢?mybatis緩存的key跟如下因素有關:
1.存儲在mappedStatement對象中的id = namespace+id(命令id);
2.查詢使用的sql語句;
3.查詢傳遞的sql實際參數值;
4.指定查詢結果集的範圍;
口說無憑,咱們看看源碼(位置:BaseExecutor-->createCacheKey()方法)
OK若是都get到了以上的點,那咱們來看看緩存技術在mybatis中如何被集成進去的
緩存的集成
先看看MapperBuilderAssistant-->useNewCache()方法
上圖CacheBuilder構建Cache對象時,採用了鏈式的編程風格。咱們來看它源碼實現
很明顯,這裏採用的是建造者設計模式對CacheBuilder的各個部分進行的分步構建,最後調用build構造出Cache對象。此模式在mybatis源碼中使用也比較多。
Builder: 抽象建造者接口,是定義建造的行爲的基礎接口;
ConcreteBuilder: 具體建造者實現類,抽象建造者行爲的實現類;
Product: 建造者模式,最終生產出的產品;
Director: 使用建造者的場景類或入口;
建造者模式的本質:對內將一個複雜對象的建立過程解耦;對外屏蔽對象建立細節的複雜度。
適合場景:對象自己比較複雜或者它的建立有多個步驟(部分)組成,通過一系列分步構建,最終組合成完整的對象的場景。
類比Cache組件,各個角色爲
它這裏並無定義基礎的建造者接口。
一級緩存調用入口: BaseExecutor-->query()方法
二級緩存調用入口: CachingExecutor---->query()方法
以上截圖代碼已經把答案說得很詳細了,相信不用我再多說。
總結
以上就是mybatis緩存組件設計和集成的整個過程。更多細節的東西,你們若是有興趣能夠多讀一下mybatis源碼。經過分析源碼你會獲得更多的收穫!若是有其它問題歡迎在文章下方留言,請繼續關注!