MyBatis 緩存機制源碼深度解析:一級緩存與二級緩存

MyBatis 緩存機制源碼深度解析:一級緩存與二級緩存

  • 一、一級緩存
    • 1.1 邏輯位置與核心源碼解析
    • 1.2 一級緩存容器:PerpetualCache
    • 1.3 createCacheKey 方法與緩存命中
    • 1.4 命中與失效時機
    • 1.5 使用方式
  • 二、二級緩存
    • 2.1 邏輯位置與核心源碼解析
    • 2.2 查詢流程、命中與失效時機
    • 2.3 使用方式
  • 三、一級緩存與二級緩存的差異

在這里插入圖片描述
Java 開發領域,MyBatis 作為主流的持久層框架,其緩存機制是提升系統性能的關鍵利器。MyBatis 提供的一級緩存二級緩存,通過不同的策略與實現,有效減少數據庫訪問次數。
本文基于 MyBatis 3.4.6 版本,結合關鍵源碼,深入解析兩種緩存的原理、使用及差異。

一、一級緩存

1.1 邏輯位置與核心源碼解析

一級緩存又稱會話級緩存,其核心邏輯主要存在于org.apache.ibatis.executor.BaseExecutor類中。BaseExecutorMyBatis 執行器的基礎抽象類,負責管理一級緩存相關操作。最外層執行的query方法,包含了緩存的核心邏輯,而doQuery方法則是具體的數據庫查詢操作,由BaseExecutor的子類(如SimpleExecutorReuseExecutor等)實現。

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);// 構造緩存keyCacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);// 執行查詢邏輯return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());// ...List<E> list;try {queryStack++;// 優先從一級緩存(localCache)中獲取結果list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {// 處理存儲過程的輸出參數緩存handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {// 一級緩存未命中,執行數據庫查詢(queryFromDatabase有具體的數據庫查詢邏輯)list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}// ...return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;// 在本地緩存中標記查詢執行中,防止循環引用導致的無限遞歸localCache.putObject(key, EXECUTION_PLACEHOLDER);try {// 執行實際的數據庫查詢操作list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}// 將查詢結果存入一級緩存localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;
}

上述代碼中,query方法先判斷緩存相關條件,嘗試從localCache獲取數據。
若緩存未命中,則調用queryFromDatabase方法執行數據庫查詢,并將結果存入一級緩存。

1.2 一級緩存容器:PerpetualCache

一級緩存的數據存儲在org.apache.ibatis.cache.PerpetualCache類實例中。PerpetualCache是一個基于 HashMap 實現的簡單緩存類,用于存儲緩存數據。

public class PerpetualCache implements Cache {private final String id;// 使用HashMap存儲緩存數據,key為緩存鍵,value為緩存值private Map<Object, Object> cache = new HashMap<Object, Object>();public PerpetualCache(String id) {this.id = id;}@Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}@Overridepublic Object getObject(Object key) {return cache.get(key);}@Overridepublic Object removeObject(Object key) {return cache.remove(key);}@Overridepublic void clear() {cache.clear();}// ...
}

PerpetualCache通過putObjectgetObject等方法實現對緩存數據的操作,BaseExecutor通過操作該實例來管理一級緩存。

1.3 createCacheKey 方法與緩存命中

CacheKey是緩存的唯一標識,MyBatis 通過createCacheKey方法生成CacheKey對象。該方法位于org.apache.ibatis.executor.BaseExecutor類,其核心邏輯是將 SQL 語句、參數、RowBounds 等信息進行哈希計算,生成唯一的緩存鍵。

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {if (closed) {throw new ExecutorException("Executor was closed.");}CacheKey cacheKey = new CacheKey();// 設置Mapper語句的唯一標識cacheKey.update(ms.getId());// 添加分頁查詢的偏移量cacheKey.update(rowBounds.getOffset());// 添加分頁查詢的限制數量cacheKey.update(rowBounds.getLimit());// 添加SQL語句本身cacheKey.update(boundSql.getSql());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();// mimic DefaultParameterHandler logicfor (ParameterMapping parameterMapping : parameterMappings) {if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();// 優先從SQL綁定參數中獲取值if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {// 通過反射獲取參數對象對應屬性的值MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}// 將參數值添加到CacheKey中cacheKey.update(value);}}if (configuration.getEnvironment() != null) {// 添加環境ID到CacheKey中cacheKey.update(configuration.getEnvironment().getId());}return cacheKey;
}

當再次執行相同 SQL 查詢時,若生成的CacheKey與緩存中已存在的CacheKey一致,且在同一個SqlSession內,就會觸發一級緩存命中,直接從緩存獲取數據。

1.4 命中與失效時機

  • 命中時機:在同一個SqlSession中,執行的 SQL 語句、輸入參數完全相同(即生成的CacheKey相同)時,一級緩存命中。

  • 失效時機SqlSession關閉,或執行insertupdatedelete操作時,一級緩存會失效。以update操作的源碼為例,在org.apache.ibatis.executor.BaseExecutor類的update方法中:

    public int update(MappedStatement ms, Object parameter) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}// 調用該方法清空一級緩存clearLocalCache();return doUpdate(ms, parameter);
    }
    

    執行update操作時,會調用clearLocalCache方法清空當前SqlSession的一級緩存,保證數據一致性。insertdelete操作也有類似邏輯。

1.5 使用方式

一級緩存默認開啟,無需額外配置。在同一個SqlSession中,MyBatis 會自動管理緩存的讀寫與失效。以下是一個簡單的 demo 案例代碼:

public class CacheTest {private SqlSessionFactory sessionFactory;/*** 加載配置文件。并且初始化SqlSessionFactory*/@Beforepublic void before() throws IOException {InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);}@Testpublic void testGetById() {SqlSession sqlSession = sessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// 第一次查詢,會執行數據庫查詢User user1 = userMapper.getUserById(6);// 第二次查詢,由于在同一個SqlSession且查詢條件相同,會命中一級緩存User user2 = userMapper.getUserById(6);System.out.println(user1 == user2); // 輸出true}
}

在這里插入圖片描述

二、二級緩存

2.1 邏輯位置與核心源碼解析

  • MyBatis 執行 SQL 時,整體流程是先經過CachingExecutor(最外層),最后才是其他ExecutorBaseExecutor子類)
  • CachingExecutor作為二級緩存的核心執行者,采用適配器模式,內部持有一個Executor對象(delegate),該delegate由具體的子類執行器(如SimpleExecutor)實例化,負責執行具體的數據庫操作*
  • MyBatis 默認未開啟二級緩存,需要在配置文件和映射文件中進行配置才能啟用。二級緩存又稱全局緩存,其核心邏輯存在于org.apache.ibatis.executor.CachingExecutor
public class CachingExecutor implements Executor {private final Executor delegate;private final TransactionalCacheManager tcm = new TransactionalCacheManager();public CachingExecutor(Executor delegate) {this.delegate = delegate;delegate.setExecutorWrapper(this);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {Cache cache = ms.getCache();// 判斷二級緩存是否開啟且可用if (cache != null) {// 處理可刷新緩存的情況,如執行增刪改操作后需要刷新緩存flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);// 優先從二級緩存中獲取結果List<E> list = (List<E>) tcm.getObject(cache, key);if (list!= null) {return list;}}}// 二級緩存未命中,委托給具體的執行器執行查詢return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}// 省略其他方法...
}

CachingExecutorquery方法中,首先通過ms.getCache()獲取當前 Mapper 語句對應的緩存對象,判斷二級緩存是否開啟。
若開啟且可用,則嘗試從二級緩存獲取數據。
若未命中,則將查詢委托給delegate,由delegate(如SimpleExecutor)調用BaseExecutor的相關方法執行具體數據庫查詢操作,并在事務提交后將結果寫入二級緩存。

2.2 查詢流程、命中與失效時機

  • 查詢流程MyBatis 先在二級緩存中查找CacheKey對應的結果,若未命中,再檢查一級緩存,若一級緩存也未命中,則執行數據庫查詢,查詢結果先存入一級緩存,事務提交后存入二級緩存。?

  • 命中時機namespace相同,執行的 SQL 語句和輸入參數相同(CacheKey相同),且事務已提交,數據已寫入二級緩存時,二級緩存命中。?

  • 失效時機:執行insertupdatedelete操作,或手動調用方法,會清空當前namespace下的二級緩存。以update操作為例,在CachingExecutor類的update方法中,通過flushCacheIfRequired方法處理緩存刷新

    public int update(MappedStatement ms, Object parameterObject) throws SQLException {flushCacheIfRequired(ms);return delegate.update(ms, parameterObject);
    }
    private void flushCacheIfRequired(MappedStatement ms) {Cache cache = ms.getCache();if (cache!= null && ms.isFlushCacheRequired()) {tcm.clear(cache);}
    }
    

    當檢測到當前 Mapper 語句配置了需要刷新緩存(ms.isFlushCacheRequired()true),就會通過TransactionalCacheManagerclear方法清空對應的二級緩存,實現緩存失效,保證數據一致性。insertdelete操作也會觸發類似的緩存清空邏輯 。

2.3 使用方式

  1. MyBatis 的配置文件中開啟二級緩存:

    <configuration><settings><setting name="cacheEnabled" value="true"/></settings>
    </configuration>
    
  2. Mapper映射文件中添加<cache>標簽啟用二級緩存,并可配置緩存屬性:

    <mapper namespace="com.coderzpw.dao.UserMapper"><cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/><select id="getUserById" resultType="com.coderzpw.domain.User">SELECT * FROM user WHERE id = #{id}</select>
    </mapper>
    
  3. 編寫測試代碼驗證二級緩存效果:

    User user1 = null;
    User user2 = null;
    try (SqlSession sqlSession1 = sessionFactory.openSession()) {UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);user1 = userMapper1.getUserById(6);sqlSession1.commit();
    }
    try (SqlSession sqlSession2 = sessionFactory.openSession()) {UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);user2 = userMapper2.getUserById(6);sqlSession2.commit();
    }
    System.out.println(user1 == user2); // 如果readOnly為true,這里會輸出true
    

三、一級緩存與二級緩存的差異

對比項一級緩存二級緩存
作用范圍SqlSession級別,會話內有效,僅在當前SqlSession中共享namespace級別,全局有效,可在多個SqlSession間共享
開啟方式默認開啟,無需配置需要在配置文件和映射文件中配置開啟
失效機制SqlSession關閉或執行增刪改操作時,通過調用clearLocalCache清空緩存執行增刪改操作或手動調用方法,通過TransactionalCacheManager清空對應namespace下的緩存
實現核心類BaseExecutorPerpetualCacheCachingExecutorTransactionalCacheManager
數據存儲位置存儲在SqlSession對應的localCache存儲在namespace對應的緩存區域,由TransactionalCacheManager管理

深入理解 MyBatis 一級緩存和二級緩存的原理與使用,有助于開發者根據業務場景靈活配置緩存策略,在提升系統性能的同時,確保數據的一致性與準確性。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/82986.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/82986.shtml
英文地址,請注明出處:http://en.pswp.cn/web/82986.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【題解-Acwing】1097. 池塘計數

題目&#xff1a;1097. 池塘計數 題目描述 農夫約翰有一片 N?M 的矩形土地。 最近&#xff0c;由于降雨的原因&#xff0c;部分土地被水淹沒了。 現在用一個字符矩陣來表示他的土地。 每個單元格內&#xff0c;如果包含雨水&#xff0c;則用”W”表示&#xff0c;如果不含…

基于Flask框架的前后端分離項目開發流程是怎樣的?

基于Flask框架的前后端分離項目開發流程可分為需求分析、架構設計、并行開發、集成測試和部署上線五個階段。以下是詳細步驟和技術要點&#xff1a; 一、需求分析與規劃 1. 明確項目邊界 功能范圍&#xff1a;確定核心功能&#xff08;如用戶認證、數據管理、支付流程&#…

板凳-------Mysql cookbook學習 (十--2)

5.12 模式匹配中的大小寫問題 mysql> use cookbook Database changed mysql> select a like A, a regexp A; ------------------------------ | a like A | a regexp A | ------------------------------ | 1 | 1 | --------------------------…

編程實驗篇--線性探測哈希表

線性探測哈希表性能測試實驗報告 1. 實驗目的 編程實現線性探測哈希表。編程測試線性探測哈希表。了解線性探測哈希表的性能特征&#xff0c;并運行程序進行驗證。 2. 實驗背景與理論基礎 哈希表是一種高效的數據結構&#xff0c;用于實現符號表&#xff08;Symbol Table&a…

使用Python提取PDF元數據的完整指南

PDF文檔中包含著豐富的元數據信息&#xff0c;這些信息對文檔管理和數據分析具有重要意義。本文將詳細介紹如何利用Python高效提取PDF元數據&#xff0c;并對比主流技術方案的優劣。 ## 一、PDF元數據概述 PDF元數據&#xff08;Metadata&#xff09;是包含在文檔中的結構化信…

【量化】策略交易類型

通過查找相關資料&#xff0c;這里羅列了一些常見的策略交易類型&#xff0c;如下&#xff1a; &#x1f4ca; 技術分析類策略 均線交叉策略&#xff08;SMA、EMA&#xff09;動量策略&#xff08;Momentum&#xff09;相對強弱指數策略&#xff08;RSI&#xff09;隨機指標策…

【Go語言基礎【17】】切片:一種動態數組

文章目錄 零、概述一、切片基礎1、切片的結構2、切片的創建方式3、切片的操作與擴容 二、切片的拷貝與共享內存三、切片作為函數參數 Go語言的切片&#xff08;slice&#xff09;是一種動態數組&#xff0c;提供了靈活、高效的元素序列操作。它基于底層數組實現&#xff0c;通過…

MybatisPlus使用DB靜態工具出現找不到實體類的報錯

報錯&#xff1a;Not Found TableInfoCache. 原因在于沒有創建實體類對應的mapper&#xff0c;并且該mapper還必須繼承baseMapper。 猜測大概的原理應該是DB會去查找實體類對應的mapper&#xff0c;然后通過mapper去查找對應的實體類。

Linux nano命令的基本使用

參考資料 GNU nanoを使いこなすnano基礎 目錄 一. 簡介二. 文件打開2.1 普通方式打開文件2.2 只讀方式打開文件 三. 文件查看3.1 打開文件時&#xff0c;顯示行號3.2 翻頁查看 四. 文件編輯4.1 Ctrl K 復制 和 Ctrl U 粘貼4.2 Alt/Esc U 撤回 五. 文件保存與退出5.1 Ctrl …

LLMs 系列科普文(15)

前面 14 篇文章&#xff0c;就是本系列科普文中想介紹的大部分技術內容。重點講述了訓練這些模型的三個主要階段和范式&#xff1a;預訓練、監督微調和強化學習。 我向你們展示了這些步驟大致對應于我們已用于教導兒童的過程。具體來說&#xff0c;我們將預訓練比作通過閱讀說…

深入理解匯編語言中的順序與分支結構

本文將結合Visual Studio環境配置、順序結構編程和分支結構實現&#xff0c;全面解析匯編語言中的核心編程概念。通過實際案例演示無符號/有符號數處理、分段函數實現和邏輯表達式短路計算等關鍵技術。 一、匯編環境配置回顧&#xff08;Win32MASM&#xff09; 在Visual Studi…

Selenium4+Python的web自動化測試框架

一、什么是Selenium&#xff1f; Selenium是一個基于瀏覽器的自動化測試工具&#xff0c;它提供了一種跨平臺、跨瀏覽器的端到端的web自動化解決方案。Selenium主要包括三部分&#xff1a;Selenium IDE、Selenium WebDriver 和Selenium Grid。 Selenium IDE&#xff1a;Firefo…

React 樣式方案與狀態方案初探

React 本身只提供了基礎 UI 層開發范式&#xff0c;其他特性的支持需要借助相關社區方案實現。本文將介紹 React 應用體系中樣式方案與狀態方案的主流選擇&#xff0c;幫助開發者根據項目需求做出合適的選擇。 1. React 樣式方案 1.1. 內聯樣式 (Inline Styles) 通過 style …

PHP中如何定義常量以及常量和變量的主要區別

在PHP編程中&#xff0c;常量和變量是存儲數據的兩種重要方式。常量在定義后值不能改變&#xff0c;而變量的值可以在程序執行過程中發生變化。本文將詳細介紹如何在PHP中定義常量&#xff0c;并深入探討常量和變量的主要區別。 一、PHP中定義常量 1. 使用 define 函數定義常…

奈飛工廠官網,國內Netflix影視在線看|中文網頁電腦版入口

奈飛工廠是一個專注于提供免費Netflix影視資源的在線播放平臺&#xff0c;致力于為國內用戶提供的Netflix熱門影視內容。該平臺的資源與Netflix官網基本同步&#xff0c;涵蓋電影、電視劇、動漫和綜藝等多個領域。奈飛工廠的界面簡潔流暢&#xff0c;資源分類清晰&#xff0c;方…

CMS內容管理系統的設計與實現:架構設計

一、整體架構方案 &#xff08;一&#xff09;架構方案選擇&#xff08;根據項目規模&#xff09; 1. 中小型項目推薦方案&#xff08;團隊<10人&#xff09; #mermaid-svg-cjzaHpptY8pYWnzo {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:1…

嵌入式里的時間魔法:RTC 與 BKP 深度拆解

文章目錄 RTC實時時鐘與BKPUnix時間戳UTC/GMT時間戳轉換時間戳轉換BKP簡介BKP基本結構1. 電池供電模塊&#xff08;VBAT 輸入&#xff09;2. 侵入檢測模塊&#xff08;TAMPER 輸入&#xff09;3. 時鐘輸出模塊&#xff08;RTC 輸出&#xff09;4. 內部寄存器組 RTC簡介RTC時鐘源…

STC8H系列 驅動步進電機

STC8H 驅動步進電機 一、引言二、硬件設計三、軟件設計Step_Motor2.c文件Step_ Motor2.h文件 一、引言 眾所周知STC8H系列有兩個PWM&#xff0c;分別為PWMA和PWMB外設模塊&#xff0c;我全都用上&#xff0c;豈不是就有兩個帶動電機的脈沖信號&#xff1f;&#xff01;哈哈哈哈…

Python高階函數:從入門到精通

目錄 Python高階函數詳解&#xff1a;從概念到高級應用引言&#xff1a;函數式編程的魅力一、高階函數基礎概念1.1 什么是高階函數1.2 Python中的一等函數 二、內置高階函數詳解2.1 map函數&#xff1a;數據轉換利器2.2 filter函數&#xff1a;數據篩選專家2.3 reduce函數&…

騰訊開源視頻生成工具 HunyuanVideo-Avatar,上傳一張圖+一段音頻,就能讓圖中的人物、動物甚至虛擬角色“活”過來,開口說話、唱歌、演相聲!

騰訊混元團隊提出的 HunyuanVideo-Avatar 是一個基于多模態擴散變換器&#xff08;MM-DiT&#xff09;的模型&#xff0c;能夠生成動態、情緒可控和多角色對話視頻。支持僅 10GB VRAM 的單 GPU運行&#xff0c;支持多種下游任務和應用。例如生成會說話的虛擬形象視頻&#xff0…