0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

聊聊MyBatis自定義緩存的原理和使用

冬至子 ? 來源:悟空聊架構(gòu) ? 作者:悟空聊架構(gòu) ? 2022-11-16 14:30 ? 次閱讀

一、MyBatis 緩存中的常用概念

MyBatis 緩存:它用來優(yōu)化 SQL 數(shù)據(jù)庫查詢的,但是可能會(huì)產(chǎn)生臟數(shù)據(jù)。

SqlSession:代表和數(shù)據(jù)庫的一次會(huì)話,向用戶提供了操作數(shù)據(jù)庫的方法。

MappedStatement:代表要發(fā)往數(shù)據(jù)庫執(zhí)行的指令,可以理解為是 SQL 的抽象表示。

Executor:代表用來和數(shù)據(jù)庫交互的執(zhí)行器,接受 MappedStatment 作為參數(shù)。

namespace:每個(gè) Mapper 文件只能配置一個(gè) namespace,用來做 Mapper 文件級別的緩存共享。

映射接口:定義了一個(gè)接口,然后里面的接口方法對應(yīng)要執(zhí)行 SQL 的操作,具體要執(zhí)行的 SQL 語句是寫在映射文件中。

映射文件:MyBatis 編寫的 XML 文件,里面有一個(gè)或多個(gè) SQL 語句,不同的語句用來映射不同的接口方法。通常來說,每一張單表都對應(yīng)著一個(gè)映射文件。

二、MyBatis 一級緩存

2.1 一級緩存原理

在一次 SqlSession 中(數(shù)據(jù)庫會(huì)話),程序執(zhí)行多次查詢,且查詢條件完全相同,多次查詢之間程序沒有其他增刪改操作,則第二次及后面的查詢可以從緩存中獲取數(shù)據(jù),避免走數(shù)據(jù)庫。

圖片

每個(gè)SqlSession中持有了Executor,每個(gè)Executor中有一個(gè)LocalCache。當(dāng)用戶發(fā)起查詢時(shí),MyBatis根據(jù)當(dāng)前執(zhí)行的語句生成MappedStatement,在Local Cache進(jìn)行查詢,如果緩存命中的話,直接返回結(jié)果給用戶,如果緩存沒有命中的話,查詢數(shù)據(jù)庫,結(jié)果寫入Local Cache,最后返回結(jié)果給用戶。

Local Cache 其實(shí)是一個(gè) hashmap 的結(jié)構(gòu):

private Map<Object, Object> cache = new HashMap<Object, Object>();

如下圖所示,有兩個(gè) SqlSession,分別為 SqlSession1 和 SqlSession2,每個(gè) SqlSession 中都有自己的緩存,緩存是 hashmap 結(jié)構(gòu),存放的鍵值對。

鍵是 SQL 語句組成的 Key :

Statement Id + Offset + Limmit + Sql + Params

值是 SQL 查詢的結(jié)果:

圖片

2.2 一級緩存配置

在 mybatis-config.xml 文件配置,name=localCacheScope,value有兩種值:SESSIONSTATEMENT

<configuration>
    <settings>
        <setting name="localCacheScope" value="SESSION"/>
    <span class="hljs-name"settings>
<configuration>

SESSION:開啟一級緩存功能

STATEMENT:緩存只對當(dāng)前執(zhí)行的這一個(gè) SQL 語句有效,也就是沒有用到一級緩存功能。

首先我們通過幾個(gè)考題來體驗(yàn)下 MyBatis 一級緩存。

2.3 一級緩存考題

考題(1)只開啟了一級緩存,下面的代碼調(diào)用了三次查詢操作 getStudentById,請判斷,下列說法正確的是?

// 打開一個(gè) SqlSession
SqlSession sqlSession = factory.openSession(true);
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); 
// 根據(jù) id=1 查詢學(xué)生信息
System.out.println(studentMapper.getStudentById(1)); 
// 根據(jù) id=1 查詢學(xué)生信息
System.out.println(studentMapper.getStudentById(1)); 
// 根據(jù) id=1 查詢學(xué)生信息
System.out.println(studentMapper.getStudentById(1));

答案:第一次從數(shù)據(jù)庫查詢到的數(shù)據(jù),第二次和第二次從 MyBatis 一級緩存查詢的數(shù)據(jù)。

解答:第一次從數(shù)據(jù)庫查詢后,后續(xù)查詢走 MyBatis 一級緩存

考題(2)只開啟了一級緩存,下面代碼示例中,開啟了一個(gè) SqlSession 會(huì)話,調(diào)用了一次查詢,然后對數(shù)據(jù)進(jìn)行了更改,又調(diào)用了一次查詢,下列關(guān)于兩次查詢的說法,正確的是?

// 打開一個(gè) SqlSession
SqlSession sqlSession = factory.openSession(true);
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); 
// 根據(jù) id=1 查詢學(xué)生信息
System.out.println(studentMapper.getStudentById(1)); 
// 插入了一條學(xué)生數(shù)據(jù),改變了數(shù)據(jù)庫
System.out.println("增加了" + studentMapper.addStudent(buildStudent()) + "個(gè)學(xué)生"); 
// 根據(jù) id=1 查詢學(xué)生信息
System.out.println(studentMapper.getStudentById(1)); 
sqlSession.close();

答案:第一次從數(shù)據(jù)庫查詢到的數(shù)據(jù),第二次從數(shù)據(jù)庫查詢的數(shù)據(jù)

解答:第一次從數(shù)據(jù)庫查詢后,后續(xù)更新(包括增刪改)數(shù)據(jù)庫中的數(shù)據(jù)后,這條 SQL 語句的緩存失效了,后續(xù)查詢需要重新從數(shù)據(jù)庫獲取數(shù)據(jù)。

考題(3)當(dāng)開啟了一級緩存,下面的代碼中,開啟了兩個(gè) SqlSession,第一個(gè) SqlSession 查詢了兩次學(xué)生 A 的姓名,第二次 SqlSession 更新了一次學(xué)生 A 的姓名,請判斷哪個(gè)選項(xiàng)符合最后的查詢結(jié)果。

SqlSession sqlSession1 = factory.openSession(true); 
SqlSession sqlSession2 = factory.openSession(true); 
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); 
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); studentMapper2.updateStudentName("B",1); 
System.out.println(studentMapper.getStudentById(1)); 
System.out.println(studentMapper2.getStudentById(1));

答案:

A
B

解答:只開啟一級緩存的情況下,SqlSession 級別是不共享的。代碼示例中,分別創(chuàng)建了兩個(gè) SqlSession,在第一個(gè) SqlSession 中查詢學(xué)生 A 的姓名,第二個(gè) SqlSession 中修改了學(xué)生 A 的姓名為 B,SqlSession2 更新了數(shù)據(jù)后,不會(huì)影響 SqlSession1,所以 SqlSession1 查到的數(shù)據(jù)還是 A。

2.4 MyBatis 一級緩存失效的場景

  1. 不同的SqlSession對應(yīng)不同的一級緩存
  2. 同一個(gè)SqlSession但是查詢條件不同
  3. 同一個(gè)SqlSession兩次查詢期間執(zhí)行了任何一次增刪改操作
  4. 同一個(gè)SqlSession兩次查詢期間手動(dòng)清空了緩存

2.5 MyBatis 一級緩存總結(jié)

  • MyBatis一級緩存內(nèi)部設(shè)計(jì)簡單,只是一個(gè)沒有容量限定的 HashMap,在緩存的功能性上有所欠缺
  • MyBatis的一級緩存最大范圍是SqlSession內(nèi)部,有多個(gè)SqlSession或者分布式的環(huán)境下,數(shù)據(jù)庫寫操作會(huì)引起臟數(shù)據(jù),建議設(shè)定緩存級別為Statement
  • 一級緩存的配置中,默認(rèn)是 SESSION 級別,即在一個(gè)MyBatis會(huì)話中執(zhí)行的所有語句,都會(huì)共享這一個(gè)緩存。

三、MyBatis 二級緩存

3.1 MyBatis 二級緩存概述

  • MyBatis的二級緩存相對于一級緩存來說,實(shí)現(xiàn)了SqlSession之間緩存數(shù)據(jù)的共享,同時(shí)粒度更加的細(xì),能夠到namespace級別,通過Cache接口實(shí)現(xiàn)類不同的組合,對Cache的可控性也更強(qiáng)。
  • MyBatis在多表查詢時(shí),極大可能會(huì)出現(xiàn)臟數(shù)據(jù),有設(shè)計(jì)上的缺陷,安全使用二級緩存的條件比較苛刻。
  • 在分布式環(huán)境下,由于默認(rèn)的MyBatis Cache實(shí)現(xiàn)都是基于本地的,分布式環(huán)境下必然會(huì)出現(xiàn)讀取到臟數(shù)據(jù),需要使用集中式緩存將 MyBatis的Cache 接口實(shí)現(xiàn),有一定的開發(fā)成本,直接使用Redis、Memcached 等分布式緩存可能成本更低,安全性也更高。

3.2 MyBatis 二級緩存原理

一級緩存最大的共享范圍就是一個(gè) SqlSession 內(nèi)部,如果多個(gè) SqlSession 之間需要共享緩存,則需要使用到二級緩存。

開啟二級緩存后,會(huì)使用 CachingExecutor 裝飾 Executor,進(jìn)入一級緩存的查詢流程前,先在CachingExecutor 進(jìn)行二級緩存的查詢。

二級緩存開啟后,同一個(gè) namespace下的所有操作語句,都影響著同一個(gè)Cache。

圖片

每個(gè) Mapper 文件只能配置一個(gè) namespace,用來做 Mapper 文件級別的緩存共享。

<mapper namespace="mapper.StudentMapper"><span class="hljs-name"mapper>

二級緩存被同一個(gè) namespace 下的多個(gè) SqlSession 共享,是一個(gè)全局的變量。MyBatis 的二級緩存不適應(yīng)用于映射文件中存在多表查詢的情況。

通常我們會(huì)為每個(gè)單表創(chuàng)建單獨(dú)的映射文件,由于MyBatis的二級緩存是基于namespace的,多表查詢語句所在的namspace無法感應(yīng)到其他namespace中的語句對多表查詢中涉及的表進(jìn)行的修改,引發(fā)臟數(shù)據(jù)問題。

3.3 MyBatis緩存查詢的順序

圖片

  • 先查詢二級緩存,因?yàn)槎壘彺嬷锌赡軙?huì)有其他程序已經(jīng)查出來的數(shù)據(jù),可以拿來直接使用
  • 如果二級緩存沒有命中,再查詢一級緩存
  • 如果一級緩存也沒有命中,則查詢數(shù)據(jù)庫
  • SqlSession關(guān)閉之后,一級緩存中的數(shù)據(jù)會(huì)寫入二級緩存。

3.4 二級緩存配置

開啟二級緩存需要在 mybatis-config.xml 中配置:


3.5 二級緩存考題

測試update操作是否會(huì)刷新該namespace下的二級緩存。

開啟了一級和二級緩存,通過三個(gè)SqlSession 查詢和更新 學(xué)生張三的姓名,判斷最后的輸出結(jié)果是什么?

SqlSession sqlSession1 = factory.openSession(true); 
SqlSession sqlSession2 = factory.openSession(true); 
SqlSession sqlSession3 = factory.openSession(true); 
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); 
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); 
StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class); System.out.println("studentMapper讀取數(shù)據(jù): " + studentMapper.getStudentById(1)); 
sqlSession1.commit(); 
System.out.println("studentMapper2讀取數(shù)據(jù): " + studentMapper2.getStudentById(1)); studentMapper3.updateStudentName("李四",1); 
sqlSession3.commit(); 
System.out.println("studentMapper2讀取數(shù)據(jù): " + studentMapper2.getStudentById(1));

答案:

張三
張三
李四

解答:三個(gè) SqlSession 是共享 MyBatis 緩存,SqlSession2 更新數(shù)據(jù)后,MyBatis 的 namespace 緩存(StudentMapper) 就失效了,SqlSession2 最后是從數(shù)據(jù)庫查詢到的數(shù)據(jù)。

四、MyBatis 自定義緩存

4.1 MyBatis 自定義緩存概述

當(dāng) MyBatis 二級緩存不能滿足要求時(shí),可以使用自定義緩存替換。(較少使用)

自定義緩存需要實(shí)現(xiàn) MyBatis 規(guī)定的接口:org.apache.ibatis.cache.Cache。這個(gè)接口里面定義了 7 個(gè)方法,我們需要自己去實(shí)現(xiàn)對應(yīng)的緩存邏輯。

圖片

4.2 整合第三方緩存 EHCache

EHCache 和 MyBatis 已經(jīng)幫我們整合好了一個(gè)自定義緩存,我們可以直接拿來用,不需要自己去實(shí)現(xiàn) MyBatis 的 org.apache.ibatis.cache.Cache 接口。

添加 mybatis-ehcache 依賴包。

<dependency>
 <groupId>org.mybatis.caches<span class="hljs-name"groupId>
 <artifactId>mybatis-ehcache<span class="hljs-name"artifactId>
 <version>1.2.1<span class="hljs-name"version>
<span class="hljs-name"dependency>

創(chuàng)建EHCache的配置文件ehcache.xml。

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    
    <diskStore path="D:\\passjava\\ehcache"/>
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    <span class="hljs-name"defaultCache>
<span class="hljs-name"ehcache>

設(shè)置二級緩存的類型,在xxxMapper.xml文件中設(shè)置二級緩存類型


4.3 EHCache配置文件說明

圖片

五、總結(jié)

本篇分別介紹了 MyBatis 一級緩存、二級緩存、自定義緩存的原理和使用,其中還穿插了 4 道考題來驗(yàn)證 MyBatis 緩存的功能。不足之處是 MyBatis 緩存源碼未分析。

審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • XML
    XML
    +關(guān)注

    關(guān)注

    0

    文章

    186

    瀏覽量

    32993
  • SQL
    SQL
    +關(guān)注

    關(guān)注

    1

    文章

    750

    瀏覽量

    43900
  • mybatis
    +關(guān)注

    關(guān)注

    0

    文章

    58

    瀏覽量

    6691
收藏 人收藏

    評論

    相關(guān)推薦

    HarmonyOS開發(fā)實(shí)例:【自定義Emitter】

    使用[Emitter]實(shí)現(xiàn)事件的訂閱和發(fā)布,使用[自定義彈窗]設(shè)置廣告信息。
    的頭像 發(fā)表于 04-14 11:37 ?880次閱讀
    HarmonyOS開發(fā)實(shí)例:【<b class='flag-5'>自定義</b>Emitter】

    自定義函數(shù)測試學(xué)習(xí)工程

    自定義函數(shù)測試學(xué)習(xí)工程
    發(fā)表于 07-01 16:37 ?5次下載

    SOPC中自定義外設(shè)和自定義指令性能分析

    SOPC中自定義外設(shè)和自定義指令性能分析 NiosII是一個(gè)建立在FPGA上的嵌入式軟核處理器,靈活性很強(qiáng)。作為體現(xiàn)NiosII靈活性精髓的兩個(gè)最主要方面,自
    發(fā)表于 03-29 15:12 ?1557次閱讀
    SOPC中<b class='flag-5'>自定義</b>外設(shè)和<b class='flag-5'>自定義</b>指令性能分析

    1602自定義字符

    1602液晶能夠顯示自定義字符,能夠根據(jù)讀者的具體情況顯示自定義字符。
    發(fā)表于 01-20 15:43 ?1次下載

    JAVA教程之自定義光標(biāo)

    JAVA教程之自定義光標(biāo),很好的學(xué)習(xí)資料。
    發(fā)表于 03-31 11:13 ?7次下載

    C#教程之自定義屏保

    C#教程之自定義屏保,很好的C#資料,快來學(xué)習(xí)吧。
    發(fā)表于 04-20 09:59 ?7次下載

    RTWconfigurationguide基于模型設(shè)計(jì)—自定義

    基于模型設(shè)計(jì)—自定義目標(biāo)系統(tǒng)配置指南,RTW自動(dòng)代碼生成相關(guān)資料。
    發(fā)表于 05-17 16:41 ?3次下載

    PDH網(wǎng)管盤 自定義字節(jié)

    PDH網(wǎng)管盤 自定義字節(jié)
    發(fā)表于 12-26 22:13 ?0次下載

    如何在android設(shè)備上安裝自定義rom

    完成后,請執(zhí)行相同的操作,但不要選擇自定義rom,而是選擇間隙。安裝間隙之前需要使用一些自定義rom,您可以從自定義rom的開發(fā)人員網(wǎng)頁上了解,如果他們沒有提及任何相關(guān)內(nèi)容,只需在自定義
    的頭像 發(fā)表于 11-05 10:48 ?4987次閱讀

    自定義視圖組件教程案例

    自定義組件 1.自定義組件-particles(粒子效果) 2.自定義組件- pulse(脈沖button效果) 3.自定義組件-progress(progress效果) 4.
    發(fā)表于 04-08 10:48 ?14次下載

    ArkUI如何自定義彈窗(eTS)

    自定義彈窗其實(shí)也是比較簡單的,通過CustomDialogController類就可以顯示自定義彈窗。
    的頭像 發(fā)表于 08-31 08:24 ?1949次閱讀

    自定義特性能做什么?

    今天跟大家分享的主題是基于自定義特性實(shí)現(xiàn)DataGridView全自動(dòng)生成。
    的頭像 發(fā)表于 02-22 16:20 ?713次閱讀
    <b class='flag-5'>自定義</b>特性能做什么?

    labview自定義控件

    labview自定義精美控件
    發(fā)表于 05-15 16:46 ?16次下載

    自定義算子開發(fā)

    一個(gè)完整的自定義算子應(yīng)用過程包括注冊算子、算子實(shí)現(xiàn)、含自定義算子模型轉(zhuǎn)換和運(yùn)行含自定義op模型四個(gè)階段。在大多數(shù)情況下,您的模型應(yīng)該可以通過使用hb_mapper工具完成轉(zhuǎn)換并順利部署到地平線芯片上……
    的頭像 發(fā)表于 04-07 16:11 ?2512次閱讀
    <b class='flag-5'>自定義</b>算子開發(fā)

    labview超快自定義控件制作和普通自定義控件制作

    labview超快自定義控件制作和普通自定義控件制作
    發(fā)表于 08-21 10:32 ?11次下載