1. 程式人生 > >MyBatis 快取機制深度解剖 / 自定義二級快取

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    :
    當快取數量達到一定大小後,將通過演算法對快取資料進行清除。預設採用 Lru 演算法(LruCache),提供有 fifo 演算法(FifoCache)等; 
              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程式碼  收藏程式碼
  1. //當呼叫 Mapper 所有的方法時,將都交由Proxy 中的 invoke 處理:  
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  3.     try {  
  4.       if (!OBJECT_METHODS.contains(method.getName())) {  
  5.         final Class declaringInterface = findDeclaringInterface(proxy, method);  
  6.         // 最終交由 MapperMethod 類處理資料庫操作,初始化 MapperMethod 物件  
  7.         final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);  
  8.         // 執行 mapper method,返回執行結果   
  9.         final Object result = mapperMethod.execute(args);  
  10.         ....  
  11.         return result;  
  12.       }  
  13.     } catch (SQLException e) {  
  14.       e.printStackTrace();  
  15.     }  
  16.     return null;  
  17.   }  

③其中的 mapperMethod 中的 execute  方法程式碼如下: 
Java程式碼  收藏程式碼
  1. public Object execute(Object[] args) throws SQLException {  
  2.     Object result;  
  3.     // 根據不同的操作類別,呼叫 DefaultSqlSession 中的執行處理  
  4.     if (SqlCommandType.INSERT == type) {  
  5.       Object param = getParam(args);  
  6.       result = sqlSession.insert(commandName, param);  
  7.     } else if (SqlCommandType.UPDATE == type) {  
  8.       Object param = getParam(args);  
  9.       result = sqlSession.update(commandName, param);  
  10.     } else if (SqlCommandType.DELETE == type) {  
  11.       Object param = getParam(args);  
  12.       result = sqlSession.delete(commandName, param);  
  13.     } else if (SqlCommandType.SELECT == type) {  
  14.       if (returnsList) {  
  15.         result = executeForList(args);  
  16.       } else {  
  17.         Object param = getParam(args);  
  18.         result = sqlSession.selectOne(commandName, param);  
  19.       }  
  20.     } else {  
  21.       throw new BindingException("Unkown execution method for: " + commandName);  
  22.     }  
  23.     return result;  
  24.   }  
由於這裡是根據 ID 進行查詢,所以最終呼叫為 sqlSession.selectOne函式。也就是接下來的的 DefaultSqlSession.selectOne 執行; 
④ ⑤ 可以在 DefaultSqlSession 看到,其 selectOne 呼叫了 selectList 方法: Java程式碼  收藏程式碼
  1. public Object selectOne(String statement, Object parameter) {  
  2.     List list = selectList(statement, parameter);  
  3.     if (list.size() == 1) {  
  4.       return list.get(0);  
  5.     }   
  6.     ...  
  7. }  
  8. public List selectList(String statement, Object parameter, RowBounds rowBounds) {  
  9.     try {  
  10.       MappedStatement ms = configuration.getMappedStatement(statement);  
  11.       // 如果啟動用了Cache 才呼叫 CachingExecutor.query,反之則使用 BaseExcutor.query 進行資料庫查詢   
  12.       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);  
  13.     } catch (Exception e) {  
  14.       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);  
  15.     } finally {  
  16.       ErrorContext.instance().reset();  
  17.     }  
  18. }  
⑥到這裡,已經執行到具體資料查詢的流程,在分析 CachingExcutor.query 前,先看看 MyBatis 中 Executor 的結構及構建過程。 


執行器(Executor):
Executor:  執行器介面。也是最終執行資料獲取及更新的例項。其類結構如下: 

BaseExecutor: 基礎執行器抽象類。實現一些通用方法,如createCacheKey 之類。並且採用 模板模式 將具體的資料庫操作邏輯(doUpdate、doQuery)交由子類實現。另外,可以看到變數 localCache: PerpetualCache,在該類採用 PerpetualCache 實現基於 Map 儲存的一級快取,其 query 方法如下: Java程式碼  收藏程式碼
  1. public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
  2.     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());  
  3.     // 執行器已關閉  
  4.     if (closed) throw new ExecutorException("Executor was closed.");  
  5.     List list;  
  6.     try {  
  7.       queryStack++;   
  8.       // 建立快取Key  
  9.       CacheKey key = createCacheKey(ms, parameter, rowBounds);   
  10.       // 從本地快取在中獲取該 key 所對應 的結果集  
  11.       final List cachedList = (List) localCache.getObject(key);   
  12.       // 在快取中找到資料  
  13.       if (cachedList != null) {   
  14.         list = cachedList;  
  15.       } else { // 未從本地快取中找到資料,開始呼叫資料庫查詢  
  16.         //為該 key 新增一個佔位標記  
  17.         localCache.putObject(key, EXECUTION_PLACEHOLDER);   
  18.         try {  
  19.           // 執行子類所實現的資料庫查詢 操作  
  20.           list = doQuery(ms, parameter, rowBounds, resultHandler);   
  21.         } finally {  
  22.           // 刪除該 key 的佔位標記  
  23.           localCache.removeObject(key);  
  24.         }  
  25.         // 將db中的資料新增至本地快取中  
  26.         localCache.putObject(key, list);  
  27.       }  
  28.     } finally {  
  29.       queryStack--;  
  30.     }  
  31.     // 重新整理當前佇列中的所有 DeferredLoad例項,更新 MateObject  
  32.     if (queryStack == 0) {   
  33.       for (DeferredLoad deferredLoad : deferredLoads) {  
  34.         deferredLoad.load();  
  35.       }  
  36.     }  
  37.     return list;  
  38.   }  
BatchExcutorReuseExcutor SimpleExcutor: 這幾個就沒什麼好說的了,繼承了 BaseExcutor 的實現其 doQuery、doUpdate 等方法,同樣都是採用 JDBC 對資料庫進行操作;三者區別在於,批量執行、重用 Statement 執行、普通方式執行。具體應用及場景在Mybatis 的文件上都有詳細說明。 

CachingExecutor: 二級快取執行器。個人覺得這裡設計的不錯,靈活地使用 delegate機制。其委託執行的類是 BaseExcutor。 當無法從二級快取獲取資料時,同樣需要從 DB 中進行查詢,於是在這裡可以直接委託給 BaseExcutor 進行查詢。其大概流程為: 

流程為: 從二級快取中進行查詢 -> [如果快取中沒有,委託給 BaseExecutor] -> 進入一級快取中查詢 -> [如果也沒有] -> 則執行 JDBC 查詢,其 query 程式碼如下: Java程式碼  收藏程式碼
  1. public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
  2.     if (ms != null) {  
  3.       // 獲取二級快取例項  
  4.       Cache cache = ms.getCache();  
  5.       if (cache != null) {  
  6.         flushCacheIfRequired(ms);  
  7.         // 獲取 讀鎖( Read鎖可由多個Read執行緒同時保持)  
  8.         cache.getReadWriteLock().readLock().lock();  
  9.         try {  
  10.           // 當前 Statement 是否啟用了二級快取  
  11.           if (ms.isUseCache()) {  
  12.             // 將建立 cache key 委託給 BaseExecutor 建立  
  13.             CacheKey key = createCacheKey(ms, parameterObject, rowBounds);  
  14.             final List cachedList = (List) cache.getObject(key);  
  15.             // 從二級快取中找到快取資料  
  16.             if (cachedList != null) {  
  17.               return cachedList;  
  18.             } else {  
  19.               // 未找到快取,很委託給 BaseExecutor 執行查詢  
  20.               List list = delegate.query(ms, parameterObject, rowBounds, resultHandler);  
  21.               tcm.putObject(cache, key, list);  
  22.               return list;  
  23.             }  
  24.           } else { // 沒有啟動用二級快取,直接委託給 BaseExecutor 執行查詢   
  25.             return delegate.query(ms, parameterObject, rowBounds, resultHandler);  
  26.           }  
  27.         } finally {  
  28.           // 當前執行緒釋放 Read 鎖  
  29.           cache.getReadWriteLock().readLock().unlock();  
  30. 相關推薦

    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的實用外掛與定義外掛

    在前面的文章中,筆者詳細介紹了 [