關(guān)于 Mybatis 緩存的那點事兒,你知道嗎?

緩存實現(xiàn)的方式

  • 一級緩存

  • 二級緩存

案例實操

1. 一級緩存

基于 PerpetualCache 的 HashMap 本地緩存(mybatis 內(nèi)部實現(xiàn) cache 接口),其存儲作用域為 Session,當(dāng) Session flush 或 close 之后,該 Session 中的所有 Cache 就將清空;

2. 二級緩存

一級緩存其機制相同,默認(rèn)也是采用 PerpetualCache 的 HashMap 存儲,不同在于其存儲作用域為 Mapper(Namespace),并且可自定義存儲源,如 Ehcache;

對于緩存數(shù)據(jù)更新機制,當(dāng)某一個作用域(一級緩存 Session/二級緩存 Namespaces)的進行了 C/R/U/D 操作后,默認(rèn)該作用域下所有 select 中的緩存將被 clear。

如果二緩存開啟,首先從二級緩存查詢數(shù)據(jù),如果二級緩存有則從二級緩存中獲取數(shù)據(jù),如果二級緩存沒有,從一級緩存找是否有緩存數(shù)據(jù),如果一級緩存沒有,查詢數(shù)據(jù)庫。

3. 二級緩存局限性

mybatis 二級緩存對細粒度的數(shù)據(jù)級別的緩存實現(xiàn)不好,對同時緩存較多條數(shù)據(jù)的緩存,比如如下需求:對商品信息進行緩存,由于商品信息查詢訪問量大,但是要求用戶每次都能查詢最新的商品信息,此時如果使用 mybatis 的二級緩存就無法實現(xiàn)當(dāng)一個商品變化時只刷新該商品的緩存信息而不刷新其它商品的信息,因為 mybaits 的二級緩存區(qū)域以 mapper 為單位劃分,當(dāng)一個商品信息變化會將所有商品信息的緩存數(shù)據(jù)全部清空

4. 一級緩存(默認(rèn)開啟)

Mybatis 默認(rèn)提供一級緩存,緩存范圍是一個 sqlSession。在同一個 SqlSession 中,兩次執(zhí)行相同的 sql 查詢,第二次不再從數(shù)據(jù)庫查詢。

原理:一級緩存采用 Hashmap 存儲,mybatis 執(zhí)行查詢時,從緩存中查詢,如果緩存中沒有從數(shù)據(jù)庫查詢。如果該 SqlSession 執(zhí)行 clearCache() 提交或者增加刪除修改操作,清除緩存。

默認(rèn)就存在,了解觀察結(jié)果即可

a.緩存存在情況(session 未提交)

@Test 

public void test01() { 

    SqlSession sqlSession=sqlSessionFactory.openSession();  

    AccountDao accountDao=sqlSession.getMapper(AccountDao.class);  

    Account account=accountDao.queryAccountById(1); 

    System.out.println(account); 

    accountDao.queryAccountById(1);  

} 

日志僅打印一條 sql

[圖片上傳失敗...(image-aff90-1600172768417)]

b.刷新緩存

Session 提交此時緩存數(shù)據(jù)被刷新

@Test 
public void test02() { 
    SqlSession sqlSession=sqlSessionFactory.openSession();  
    AccountDao accountDao=sqlSession.getMapper(AccountDao.class);  
    Account account=accountDao.queryAccountById(1); 
    System.out.println(account); 
    sqlSession.clearCache(); 
    accountDao.queryAccountById(1);  
} 

效果:

[圖片上傳失敗...(image-7f44b7-1600172768418)]

5. 二級緩存

一級緩存是在同一個 sqlSession 中,二級緩存是在同一個 namespace 中,因此相同的 namespace 不同的 sqlsession 可以使用二級緩存。

使用場景

  • 對查詢頻率高,變化頻率低的數(shù)據(jù)建議使用二級緩存。

  • 對于訪問多的查詢請求且用戶對查詢結(jié)果實時性要求不高,此時可采用 mybatis 二級緩存技術(shù)降低數(shù)據(jù)庫訪問量,提高訪問速度,業(yè)務(wù)場景比如:耗時較高的統(tǒng)計分析 sql、電話賬單查詢 sql 等。

全局文件配置(mybatis.xml)

<setting name="cacheEnabled" value="true"/> 
Mapper.xml 中加入 :打開該 mapper 的二級緩存 

<!-- 開啟該 mapper 的二級緩存 --> 

<cache/>

cache 標(biāo)簽常用屬性

<cache  

eviction="FIFO" <!--回收策略為先進先出--> 

flushInterval="60000" <!--自動刷新時間 60s--> 

size="512" <!--最多緩存 512 個引用對象--> 

readOnly="true"/> <!--只讀--> 

說明:

  1. 映射語句文件中的所有 select 語句將會被緩存。

  2. 映射語句文件中的所有 insert,update 和 delete 語句會刷新緩存。

  3. 緩存會使用 Least Recently Used(LRU,最近最少使用的)算法來收回。

  4. 緩存會根據(jù)指定的時間間隔來刷新.

  5. 緩存會存儲 1024 個對象

PO 對象必須支持序列化

public class User implements Serializable { 

} 

關(guān)閉 Mapper 下的具體的 statement 的緩存

使用 useCache:默認(rèn)為 true

<select id="findUserByid" parameterType="int" resultType="User"  

useCache="false"> 

    SELECT * FROM user WHERE id=#{id} 

</select> 

**刷新二級緩存 **

操作 CUD 的 statement 時候,會強制刷新二級緩存 即默認(rèn) flushCache="true" ,如果想關(guān)閉設(shè)定為 flushCache="false"即可 ,不建議關(guān)閉刷新,因為操作更新刪除修改,關(guān)閉后容易獲取臟數(shù)據(jù)。

二級緩存測試:

@Test 

public void test03() { 

    SqlSession sqlSession=sqlSessionFactory.openSession();  

    AccountDao accountDao=sqlSession.getMapper(AccountDao.class);  

    Account account=accountDao.queryAccountById(1); 

    System.out.println(account); 

    sqlSession.close(); 

    SqlSession sqlSession2=sqlSessionFactory.openSession(); 

    AccountDao accountDao2=sqlSession2.getMapper(AccountDao.class);  

    accountDao2.queryAccountById(1); 

    sqlSession.close(); 

} 

擴展

分布式緩存 ehcache

如果有多條服務(wù)器 ,不使用分布緩存,緩存的數(shù)據(jù)在各個服務(wù)器單獨存儲,不方便系統(tǒng)開發(fā)。所以要使用分布式緩存對緩存數(shù)據(jù)進行集中管理。因此可是使用 ehcache memcached redis

mybatis 本身來說是無法實現(xiàn)分布式緩存的,所以要與分布式緩存框架進行整合。 EhCache 是一個純 Java 的進程內(nèi)緩存框架,具有快速、精干等特點;Ehcache 是一種廣泛 使用的開源 Java 分布式緩存。主要面向通用緩存,Java EE 和輕量級容器。它具有內(nèi)存和磁盤存儲,緩存加載器,緩存擴展,緩存異常處理程序,一個 gzip 緩存 servlet 過濾器,支持 REST 和 SOAP api 等特點。

Jar 依賴

<dependency> 

    <groupId>net.sf.ehcache</groupId> 

    <artifactId>ehcache-core</artifactId> 

    <version>2.4.4</version> 

</dependency> 

<dependency> 

    <groupId>org.mybatis.caches</groupId> 

    <artifactId>mybatis-ehcache</artifactId> 

    <version>1.0.3</version> 

</dependency> 

緩存接口配置

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/> 

在 src 下 加入 ehcache.xml(不是必須的沒有使用默認(rèn)配置)

<?xml version="1.0" encoding="UTF-8"?> 

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

xsi:noNamespaceSchemaLocation="../bin/ehcache.xsd"> 

<!-- 

name:Cache 的唯一標(biāo)識 

maxElementsInMemory:內(nèi)存中最大緩存對象數(shù) 

maxElementsOnDisk:磁盤中最大緩存對象數(shù),若是 0 表示無窮大 

eternal:Element 是否永遠不過期,如果為 true,則緩存的數(shù)據(jù)始終有效,如果為 false 

那么還要根據(jù) timeToIdleSeconds,timeToLiveSeconds 判斷 

overflowToDisk:配置此屬性,當(dāng)內(nèi)存中 Element 數(shù)量達到 maxElementsInMemory 時, 

Ehcache 將會 Element 寫到磁盤中 

timeToIdleSeconds:設(shè)置 Element 在失效前的允許閑置時間。僅當(dāng) element 不是永久有效 

時使用,可選屬性,默認(rèn)值是 0,也就是可閑置時間無窮大 

timeToLiveSeconds:設(shè)置 Element 在失效前允許存活時間。最大時間介于創(chuàng)建時間和失效 

時間之間。僅當(dāng) element 不是永久有效時使用,默認(rèn)是 0.,也就是 element 存活時間無窮 

大 

diskPersistent:是否緩存虛擬機重啟期數(shù)據(jù) 

diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認(rèn)是 120 秒 

diskSpoolBufferSizeMB:這個參數(shù)設(shè)置 DiskStore(磁盤緩存)的緩存區(qū)大小。默認(rèn)是 

30MB。每個 Cache 都應(yīng)該有自己的一個緩沖區(qū) 

memoryStoreEvictionPolicy:當(dāng)達到 maxElementsInMemory 限制時,Ehcache 將會根據(jù) 

指定的策略去清理內(nèi)存。默認(rèn)策略是 LRU(最近最少使用)。你可以設(shè)置為 FIFO(先進先 

出)或是 LFU(較少使用) 

--> 

<defaultCache overflowToDisk="true" eternal="false"/> 

<diskStore path="D:/cache" /> 

<!-- 

<cache name="sxtcache" overflowToDisk="true" eternal="false" 

timeToIdleSeconds="300" timeToLiveSeconds="600" maxElementsInMemory="1000" 

maxElementsOnDisk="10" diskPersistent="true"  

diskExpiryThreadIntervalSeconds="300" 

diskSpoolBufferSizeMB="100" memoryStoreEvictionPolicy="LRU" /> 

--> 

測試:

@Test 

public void test04() { 

    SqlSession sqlSession=sqlSessionFactory.openSession();  

    AccountDao accountDao=sqlSession.getMapper(AccountDao.class);  

    Account account=accountDao.queryAccountById(1); 

    System.out.println(account); 

    sqlSession.close(); 

    SqlSession sqlSession2=sqlSessionFactory.openSession(); 

    AccountDao accountDao2=sqlSession2.getMapper(AccountDao.class);  

    accountDao2.queryAccountById(1); 

    sqlSession.close(); 

} 

效果:

Cache Hit Ratio [com.xxx.dao.AccountDao]:0.5
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容