MyBatis 快取機制深度解剖 / 自定義二級快取
感謝有奉獻精神的人
轉自:http://denger.iteye.com/blog/1126423/
快取概述
- 正如大多數持久層框架一樣,MyBatis 同樣提供了一級快取和二級快取的支援;
- 一級快取基於 PerpetualCache 的 HashMap 本地快取,其儲存作用域為 Session,當 Session flush 或 close 之後,該Session中的所有 Cache 就將清空。
- 二級快取與一級快取其機制相同,預設也是採用 PerpetualCache,HashMap儲存,不同在於其儲存作用域為 Mapper(Namespace),並且可自定義儲存源,如 Ehcache、Hazelcast等。
- 對於快取資料更新機制,當某一個作用域(一級快取Session/二級快取Namespaces)的進行了 C/U/D 操作後,預設該作用域下所有 select 中的快取將被clear。
- MyBatis 的快取採用了delegate機制 及 裝飾器模式設計,當put、get、remove時,其中會經過多層 delegate cache 處理,其Cache類別有:BaseCache(基礎快取)、EvictionCache(排除演算法快取) 、DecoratorCache(裝飾器快取): BaseCache
:為快取資料最終儲存的處理類,預設為 PerpetualCache,基於Map儲存;可自定義儲存處理,如基於EhCache、Memcached等;
EvictionCache :
DecoratorCache:快取put/get處理前後的裝飾器,如使用 LoggingCache 輸出快取命中日誌資訊、使用 SerializedCache 對 Cache的資料 put或get 進行序列化及反序列化處理、當設定flushInterval(預設1/h)後,則使用 ScheduledCache 對快取資料進行定時重新整理等。 - 一般快取框架的資料結構基本上都是 Key-Value 方式儲存,MyBatis 對於其 Key 的生成採取規則為:[hashcode : checksum : mappedStementId : offset : limit : executeSql : queryParams]。
- 對於併發 Read/Write 時快取資料的同步問題,MyBatis 預設基於 JDK/concurrent中的ReadWriteLock,使用ReentrantReadWriteLock 的實現,從而通過 Lock 機制防止在併發 Write Cache 過程中執行緒安全問題。
原始碼剖解
接下來將結合 MyBatis 序列圖進行原始碼分析。在分析其Cache前,先看看其整個處理過程。
執行過程:
① 通常情況下,我們需要在 Service 層呼叫 Mapper Interface 中的方法實現對資料庫的操作,上述根據產品 ID 獲取 Product 物件。
② 當呼叫 ProductMapper 時中的方法時,其實這裡所呼叫的是 MapperProxy 中的方法,並且 MapperProxy已經將將所有方法攔截,其具體原理及分析,參考 MyBatis+Spring基於介面程式設計的原理分析,其 invoke 方法程式碼為:
Java程式碼
- //當呼叫 Mapper 所有的方法時,將都交由Proxy 中的 invoke 處理:
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- try {
- if (!OBJECT_METHODS.contains(method.getName())) {
- final Class declaringInterface = findDeclaringInterface(proxy, method);
- // 最終交由 MapperMethod 類處理資料庫操作,初始化 MapperMethod 物件
- final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
- // 執行 mapper method,返回執行結果
- final Object result = mapperMethod.execute(args);
- ....
- return result;
- }
- } catch (SQLException e) {
- e.printStackTrace();
- }
- return null;
- }
③其中的 mapperMethod 中的 execute 方法程式碼如下:
Java程式碼
- public Object execute(Object[] args) throws SQLException {
- Object result;
- // 根據不同的操作類別,呼叫 DefaultSqlSession 中的執行處理
- if (SqlCommandType.INSERT == type) {
- Object param = getParam(args);
- result = sqlSession.insert(commandName, param);
- } else if (SqlCommandType.UPDATE == type) {
- Object param = getParam(args);
- result = sqlSession.update(commandName, param);
- } else if (SqlCommandType.DELETE == type) {
- Object param = getParam(args);
- result = sqlSession.delete(commandName, param);
- } else if (SqlCommandType.SELECT == type) {
- if (returnsList) {
- result = executeForList(args);
- } else {
- Object param = getParam(args);
- result = sqlSession.selectOne(commandName, param);
- }
- } else {
- throw new BindingException("Unkown execution method for: " + commandName);
- }
- return result;
- }
④ ⑤ 可以在 DefaultSqlSession 看到,其 selectOne 呼叫了 selectList 方法: Java程式碼
- public Object selectOne(String statement, Object parameter) {
- List list = selectList(statement, parameter);
- if (list.size() == 1) {
- return list.get(0);
- }
- ...
- }
- public List selectList(String statement, Object parameter, RowBounds rowBounds) {
- try {
- MappedStatement ms = configuration.getMappedStatement(statement);
- // 如果啟動用了Cache 才呼叫 CachingExecutor.query,反之則使用 BaseExcutor.query 進行資料庫查詢
- return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
- } catch (Exception e) {
- throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
- } finally {
- ErrorContext.instance().reset();
- }
- }
執行器(Executor):
Executor: 執行器介面。也是最終執行資料獲取及更新的例項。其類結構如下:
BaseExecutor: 基礎執行器抽象類。實現一些通用方法,如createCacheKey 之類。並且採用 模板模式 將具體的資料庫操作邏輯(doUpdate、doQuery)交由子類實現。另外,可以看到變數 localCache: PerpetualCache,在該類採用 PerpetualCache 實現基於 Map 儲存的一級快取,其 query 方法如下: Java程式碼
- public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
- ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
- // 執行器已關閉
- if (closed) throw new ExecutorException("Executor was closed.");
- List list;
- try {
- queryStack++;
- // 建立快取Key
- CacheKey key = createCacheKey(ms, parameter, rowBounds);
- // 從本地快取在中獲取該 key 所對應 的結果集
- final List cachedList = (List) localCache.getObject(key);
- // 在快取中找到資料
- if (cachedList != null) {
- list = cachedList;
- } else { // 未從本地快取中找到資料,開始呼叫資料庫查詢
- //為該 key 新增一個佔位標記
- localCache.putObject(key, EXECUTION_PLACEHOLDER);
- try {
- // 執行子類所實現的資料庫查詢 操作
- list = doQuery(ms, parameter, rowBounds, resultHandler);
- } finally {
- // 刪除該 key 的佔位標記
- localCache.removeObject(key);
- }
- // 將db中的資料新增至本地快取中
- localCache.putObject(key, list);
- }
- } finally {
- queryStack--;
- }
- // 重新整理當前佇列中的所有 DeferredLoad例項,更新 MateObject
- if (queryStack == 0) {
- for (DeferredLoad deferredLoad : deferredLoads) {
- deferredLoad.load();
- }
- }
- return list;
- }
CachingExecutor: 二級快取執行器。個人覺得這裡設計的不錯,靈活地使用 delegate機制。其委託執行的類是 BaseExcutor。 當無法從二級快取獲取資料時,同樣需要從 DB 中進行查詢,於是在這裡可以直接委託給 BaseExcutor 進行查詢。其大概流程為:
流程為: 從二級快取中進行查詢 -> [如果快取中沒有,委託給 BaseExecutor] -> 進入一級快取中查詢 -> [如果也沒有] -> 則執行 JDBC 查詢,其 query 程式碼如下: Java程式碼
- public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
- if (ms != null) {
- // 獲取二級快取例項
- Cache cache = ms.getCache();
- if (cache != null) {
- flushCacheIfRequired(ms);
- // 獲取 讀鎖( Read鎖可由多個Read執行緒同時保持)
- cache.getReadWriteLock().readLock().lock();
- try {
- // 當前 Statement 是否啟用了二級快取
- if (ms.isUseCache()) {
- // 將建立 cache key 委託給 BaseExecutor 建立
- CacheKey key = createCacheKey(ms, parameterObject, rowBounds);
- final List cachedList = (List) cache.getObject(key);
- // 從二級快取中找到快取資料
- if (cachedList != null) {
- return cachedList;
- } else {
- // 未找到快取,很委託給 BaseExecutor 執行查詢
- List list = delegate.query(ms, parameterObject, rowBounds, resultHandler);
- tcm.putObject(cache, key, list);
- return list;
- }
- } else { // 沒有啟動用二級快取,直接委託給 BaseExecutor 執行查詢
- return delegate.query(ms, parameterObject, rowBounds, resultHandler);
- }
- } finally {
- // 當前執行緒釋放 Read 鎖
- cache.getReadWriteLock().readLock().unlock();
-
相關推薦
MyBatis 快取機制深度解剖 / 自定義二級快取
感謝有奉獻精神的人 轉自:http://denger.iteye.com/blog/1126423/ 快取概述 正如大多數持久層框架一樣,MyBatis 同樣提供了一級快取和二級快取的支援;一級快取基於 PerpetualCache 的
Springboot 2.0.x Redis快取Key生成器,自定義生成器
文章目錄 Springboot 2.0.x Redis快取Key生成器,自定義生成器 1、預設的Key生成策略 2、重寫生成器 3、註冊自定義生成器 4、應用
Android中RxJava使用9----自定義圖片快取框架
操作符:concat 不交錯的發射兩個或多個Observable的發射物 原理: 圖片快取框架,原理 1)檢查圖片是否在記憶體中快取 2)如果不在,檢查圖片是否在檔案中快取 3)如果不在,則從網路上拿圖片 下面程式碼只說明原理,真正實現功能,下載原始碼 1、在
隨筆(八) 自定義redis快取註解(基於springboot)
前言: 最近專案開發中需要使用redis快取為資料庫降壓。由於在構建系統時沒有使用快取,後期加入快取的時候不想對業務程式碼上新增,造成程式碼入侵,所有封裝了一套自定義快取類,處理快取。 開發環境: win10+Intelli
mybatis-generator擴充套件教程系列 -- 自定義generatorConfig.xml引數
今天我打算講如何在生成器的xml配置檔案里加入自定義的引數,真實很多場景我們會遇到需要自定義BaseDAO,BaseService類路徑,所以這個時候為了擴充套件我們會考慮把這些引數放到xml配置,下面就延續使用上一篇的教程專案來做程式碼示例(如果沒有看過之前
Java 自定義 物件快取
快取:把所有使用者的資料統一放到一個地方,為每個使用者的快取取一個名字來標示它,存的時候不要讓你的快取放上了別人的名字,取得時候不要讓你取了別人的名字的資料.這就要求你的執行緒做到執行緒同步. 效果圖: 程式碼: package com.kerun.app.util
mybatis-generator擴充套件教程系列 -- 自定義sql xml檔案
今天抽空寫一下生成器比較重要的環節,如何自定義mybatis生成器的sql xml檔案,因為原生出來的格式不好看,命名也不符合我們日常使用習慣,很多冗餘的sql節點,下面我直接直入主題演示程式碼了,還是老規矩使用之前教程延續下來的專案用例1.先看看我們原始生
spring-boot整合redis作為快取(3)——自定義key
分幾篇文章總結spring-boot與Redis的整合 4、自定義key 5、spring-boot引入Redis 在上一篇文章中說道key是用來分辨同一個快取中的快取資料的。key是可以自己制定的,也
Shiro登入機制驗證,自定義FormAuthenticationFilter
自定義登入form攔截器:org.apache.shiro.web.filter.authc.FormAuthenticationFilter 問題描述 使用shiro進行系統身份驗證-許可權控制,登入介面進行登入操作何時觸發 問題分析 探知login.js
[Swift通天遁地]五、高階擴充套件-(11)影象載入Loading動畫效果的自定義和快取
本文將演示影象載入Loading動畫效果的自定義和快取。 首先確保在專案中已經安裝了所需的第三方庫。 點選【Podfile】,檢視安裝配置檔案。 1 platform :ios, '12.0' 2 use_frameworks! 3 4 target 'DemoApp' do 5
MyBatis 延遲載入,一級快取(sqlsession級別)、二級快取(mapper級別)設定
什麼是延遲載入 resultMap中的association和collection標籤具有延遲載入的功能。 延遲載入的意思是說,在關聯查詢時,利用延遲載入,先載入主資訊。使用關聯資訊時再去載入關聯資訊。 設定延遲載入
Mybatis-generator修改原始碼實現自定義方法,返回List物件(三)
前兩篇文章我們講了如何獲取原始碼即建立工程、修改原始碼為dao(mapper)層新增一個方法,那麼這一篇,我們來講如何在xml新增這個方法所需要sql 3、實現XML檔案新增Dao(Mapper)層的實現 前面有講過,下圖中的兩個包,分別是管理dao(M
mybatis 中 查詢結果進行自定義的配置
當mybatis的結果集是可以對映成為一個entity的時候, 這個時候的配置是比較簡單的 resultType=”org.huluo.Entity”直接是包名加上類名就可以了。 但是如果select出來的東西有groupby 和entity屬性不匹配的
mybatis 最簡單的執行自定義SQL語句
最近有個同事要包裝一個可以執行sql語句的功能用的是mybatis 最開始他想到的方案是拿到資料庫連線再執行sql語句。 後來出了某些錯誤來問我,為了尋求比較快的解決方法於是我就試試了下下面的方法。
Mybatis-generator修改原始碼實現自定義方法,返回List物件(二)
上一篇我們講了如何獲取Mybatis-generator的原始碼和建立工程,以及通過main方法來生成XML、實體類、mapper檔案,這一篇我們來講通過修改程式碼來為mapper新增一個方法 2、組合原始碼中的示例,實現Dao(Mapper)層新增一個
mybatis-generator擴充套件教程系列 -- 自定義配置引數修改DAO,Mapper檔案字尾
今天主要講解如何解決我們使用mybatis生成器遇到的最常見問題,如何修改生成的dao,mapper檔案字尾,下面我們繼續使用上一篇的用例繼續改造,如果本篇示例看得不太理解的可以翻看下之前的演示,下面就開始直奔主題了1. 先增加一個引數配置看我們的檔案生成字
Android兩級導航選單欄的實現--FragmentTabHost+自定義二級導航選單欄
前兩篇博文分別採用 FragmentTabHost巢狀FragmentTabHost和FragmentTabHost+PagerSlidingTabStrip 與ViewPager的方式實現了子Tab導航選單欄的效果,雖是好用,但有時候卻不靈活。 本篇中將
Mybatis-generator修改原始碼實現自定義方法,返回List物件(一)
Mybatis-generator修改原始碼實現自定義方法,返回Lsit物件——第一篇 本文結合網上的諸多教程,詳細介紹通過修改Mybatis-generator的原始碼, 在自動生成dao層和XML檔案時,新增一個返回List的方法,資料庫使用Mysql
SpringBoot使用AOP實現自定義介面快取
一、引入pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data
淺析MyBatis(三):聊一聊MyBatis的實用外掛與自定義外掛
在前面的文章中,筆者詳細介紹了 [