Mybatis 一級緩存 、二級緩存

https://baijiahao.baidu.com/s?id=1578214153704160256&wfr=spider&for=pc

 

在介紹Mybatis一級緩存和二級緩存之前,需要首先理解兩個概念:

SqlSession:引用官方文檔中對這個接口作用的說明—SqlSession完全包含了面向數據庫執行SQL命令所需的所有方法。你可以通過SqlSession實例來直接執行已映射的SQL語句,也可以通過SqlSession得到映射和管理事務。

namespace:這裏提到的namespace指代的是在應用中配置的mapper配置文件中的namespace。

eg:

<mapper namespace=”xxx”></mapper>

下面開始正式介紹Mybatis的一級和二級緩存。

一級緩存:SqlSession維度的緩存,也就是每個SqlSession獨享的緩存,我們在使用Mybatis的時候,通常會使用SqlSession的getMapper方法獲取到映射。

UserDao userDao = sqlSession.getMapper(UserDao.class);

獲取到映射之後執行相關方法進行數據庫操作,獲取到的映射對象是通過動態代理生成的代理類MapperProxy對象,Mybatis執行SQL的過程不是本文的重點,這裏不過多贅述,有興趣的讀者可以自行查閱Mybatis源碼。Mybatis的數據庫操作是通過Executor來執行,一級緩存也是通過Executor來維護,每個SqlSession都會持有一個Executor:

圖中LocalCache就是Mybatis的一級緩存,LocalCache的查詢和寫入是在Executor內部完成的,BaseExecutor是一個實現了Executor接口的抽象類,通過閱讀源碼發現,一級緩存LocalCache就是BaseExecutor內部的一個成員變量。

public abstract class BaseExecutor implements Executor {protected PerpetualCache localCache;public class PerpetualCache implements Cache {private Map<Object, Object> cache = HashMap<Object, Object>();

一級緩存保存在PerpetualCache維護的一個Map中,下面爲一級緩存執行的流程圖:

從流程圖中看到,在執行Executor query動作時會嘗試從一級緩存中獲取緩存數據,下面爲相關源碼:

在BaseExecutor的query方法中發現了緩存key的構建過程,Mybatis用CacheKey這個對象作爲緩存的key,上文中說到一級緩存最終會保存在PerpetualCache維護的一個Map中,我們知道,Map用hashcode和equals來確定對象的唯一性,在CacheKey的update方法中,我們發現了hashcode的運算:

update方法會計算hashcode和checksum並將參數對象保存到了updateList中,而在CacheKey的equals方法中,除去hashcode、checksum和count的比較外,只要updatelist中的元素一一對應相等,那麼就可以認爲是相等:

所以,只要知道創建Cachekey時updateList中都存放了哪些元素,就可以知道Mybatis是通過哪些因素來確定緩存唯一性,在上文標註的createCacheKey方法中,我們找到了這些元素:

繼續分析上文中提到的query方法中調用的重載query方法:

在query方法中發現了清空一級緩存的兩處代碼,這兩處的判斷在Mybatis中都是可以配置的。可以在mapper配置文件中配置flushCache來控制數據庫操作是否要清空緩存,select默認爲false,insert、update、delete默認爲true,注意,這個配置一級緩存和二級緩存都會生效,下文會介紹二級緩存。可以使用localCacheScope選項來控制一級緩存的範圍,默認爲SESSION,會緩存一個SqlSession中的所有查詢,如果設置爲STATEMENT,則查詢會清除緩存。在查詢數據庫queryFromDatabase方法中,Mybatis做數據庫查詢操作,並將返回的結果存入一級緩存:

數據庫的寫操作會清除緩存,同時確認的是insert、update、delete都會統一走update方法:

二級緩存:namespace維度的緩存。上文提到SqlSession中會持有一個Executor,在構建SqlSession的時候,Mybatis會根據cacheEnable選項來確定是否使用一個緩存的Executor,也就是CachingExecutor。cacheEnable可以配置,默認爲true:

cacheEnable開啓的狀態下在mapper配置文件中配置<cache/>和<cache-ref/>可以控制緩存策略,介紹一級緩存的時候我們提到了可以使用flushCache選項來控制是否清空緩存,這個配置同樣會作用於二級緩存。mybatis在解析配置文件的時候,會將<cache/>配置的參數解析成Cache對象保存到MappedStatement(可以理解爲mapper配置文件的java代碼實現,對象中保存了在mapper配置文件中配置的選項)中,解析配置文件的過程不是本文的內容,感興趣的讀者可以自行查看Mybatis源碼。構建好SqlSession後,數據庫操作就會委託給CachingExcutor進行執行:

這是一個典型的按需加載緩存的方式,注意,tcm.putObject方法執行完之後緩存並沒有真正的生效,這裏只是記錄了這次查詢將要產生緩存變更,這時候相同的sql查詢緩存是不會生效的。同樣的,寫操作也不是馬上會清除緩存:

在執行SqlSession的commit方法之後,緩存的變更會真正的被刷新到緩存中去,開始真正的發揮作用:

我們看到在保存緩存和刷新緩存時用到的delegate對象就是上文中提到的構造TransactionalCache對象時傳入的Cache對象了,在構建Cache對象時,Mybatis採用裝飾者模式爲Cache裝飾了不同的功能,有興趣的讀者可以閱讀相關源碼來了解這部分的內容。緩存保存在PerpetualCache中:

到這裏,整個Mybatis緩存的分析就結束了,最後我們分析一下Mybatis緩存到底有什麼“坑”。在介紹一級緩存時我們提到Mybatis的一級緩存是SqlSession級別的緩存,不同的SqlSession之間緩存是不共享的,如果兩個SqlSession操作同一張表,這時候就可能出現其中一個SqlSession獲取到的數據是過期的,我們在使用這個SqlSession查詢就有可能讀取過期的髒數據。在介紹二級緩存時我們提到二級緩存是namespace維度的緩存,全局共享整個namespace的緩存,當我們把針對同一張表的sql操作寫到兩個不同的mapper文件中或者使用表關聯查詢時,就很容易出現兩個mapper中查詢出來的同一條數據不一致的情況。所以在實際應用中,我們建議將cacheEnable設置爲false、localCacheScope設置爲STATEMENT,不使用mybatis的緩存,需要緩存的時候用應用程序中的緩存來控制來避免Mybatis的緩存坑。

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