Mybatis 緩存的那點(diǎn)事兒,你知道嗎?

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

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

一級(jí)緩存

二級(jí)緩存

案例實(shí)操

1. 一級(jí)緩存

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

2. 二級(jí)緩存

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

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

如果二緩存開(kāi)啟,首先從二級(jí)緩存查詢(xún)數(shù)據(jù),如果二級(jí)緩存有則從二級(jí)緩存中獲取數(shù)據(jù),如果二級(jí)緩存沒(méi)有,從一級(jí)緩存找是否有緩存數(shù)據(jù),如果一級(jí)緩存沒(méi)有,查詢(xún)數(shù)據(jù)庫(kù)

3.? 二級(jí)緩存局限性

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

4. 一級(jí)緩存(默認(rèn)開(kāi)啟)

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

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

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

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

@Test

?

publicvoidtest01() {

?

SqlSessionsqlSession=sqlSessionFactory.openSession();

?

AccountDaoaccountDao=sqlSession.getMapper(AccountDao.class);

?

Accountaccount=accountDao.queryAccountById(1);

?

System.out.println(account);

?

accountDao.queryAccountById(1);

?

}

日志僅打印一條 sql

b.刷新緩存

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

@Test

publicvoidtest02() {

SqlSessionsqlSession=sqlSessionFactory.openSession();

AccountDaoaccountDao=sqlSession.getMapper(AccountDao.class);

Accountaccount=accountDao.queryAccountById(1);

System.out.println(account);

sqlSession.clearCache();

accountDao.queryAccountById(1);

}

效果:

5. 二級(jí)緩存

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

使用場(chǎng)景

對(duì)查詢(xún)頻率高,變化頻率低的數(shù)據(jù)建議使用二級(jí)緩存。

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

全局文件配置(mybatis.xml)

<settingname="cacheEnabled"value="true"/>

Mapper.xml 中加入 :打開(kāi)該 mapper 的二級(jí)緩存

?

<!-- 開(kāi)啟該 mapper 的二級(jí)緩存 -->

?

<cache/>

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

<cache

?

eviction="FIFO"<!--回收策略為先進(jìn)先出-->

?

flushInterval="60000"<!--自動(dòng)刷新時(shí)間 60s-->

?

size="512"<!--最多緩存 512 個(gè)引用對(duì)象-->

?

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

說(shuō)明:

映射語(yǔ)句文件中的所有 select 語(yǔ)句將會(huì)被緩存。

映射語(yǔ)句文件中的所有 insert,update 和 delete 語(yǔ)句會(huì)刷新緩存。

緩存會(huì)使用 Least Recently Used(LRU,最近最少使用的)算法來(lái)收回。

緩存會(huì)根據(jù)指定的時(shí)間間隔來(lái)刷新.

緩存會(huì)存儲(chǔ) 1024 個(gè)對(duì)象

PO 對(duì)象必須支持序列化

publicclassUserimplementsSerializable{

?

}

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

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

<selectid="findUserByid"parameterType="int"resultType="User"

?

useCache="false">

?

? ? SELECT * FROM user WHERE id=#{id}

?

</select>

刷新二級(jí)緩存

操作 CUD 的 statement 時(shí)候,會(huì)強(qiáng)制刷新二級(jí)緩存 即默認(rèn) flushCache="true" ,如果想關(guān)閉設(shè)定為 flushCache="false"即可 ,不建議關(guān)閉刷新,因?yàn)椴僮鞲聞h除修改,關(guān)閉后容易獲取臟數(shù)據(jù)。

二級(jí)緩存測(cè)試:

@Test

?

publicvoidtest03() {

?

SqlSessionsqlSession=sqlSessionFactory.openSession();

?

AccountDaoaccountDao=sqlSession.getMapper(AccountDao.class);

?

Accountaccount=accountDao.queryAccountById(1);

?

System.out.println(account);

?

sqlSession.close();

?

SqlSessionsqlSession2=sqlSessionFactory.openSession();

?

AccountDaoaccountDao2=sqlSession2.getMapper(AccountDao.class);

?

accountDao2.queryAccountById(1);

?

sqlSession.close();

?

}

效果:

擴(kuò)展

分布式緩存 ehcache

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

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

Jar 依賴(lài)

<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>

緩存接口配置

<cachetype="org.mybatis.caches.ehcache.EhcacheCache"/>

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

<?xmlversion="1.0" encoding="UTF-8"?>

?

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

?

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

?

<!--

?

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

?

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

?

maxElementsOnDisk:磁盤(pán)中最大緩存對(duì)象數(shù),若是 0 表示無(wú)窮大

?

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

?

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

?

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

?

Ehcache 將會(huì) Element 寫(xiě)到磁盤(pán)中

?

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

?

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

?

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

?

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

?

?

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

?

diskExpiryThreadIntervalSeconds:磁盤(pán)失效線(xiàn)程運(yùn)行時(shí)間間隔,默認(rèn)是 120 秒

?

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

?

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

?

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

?

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

?

出)或是 LFU(較少使用)

?

-->

?

<defaultCacheoverflowToDisk="true"eternal="false"/>

?

<diskStorepath="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" />

?

-->

測(cè)試:

@Test

?

publicvoidtest04() {

?

SqlSessionsqlSession=sqlSessionFactory.openSession();

?

AccountDaoaccountDao=sqlSession.getMapper(AccountDao.class);

?

Accountaccount=accountDao.queryAccountById(1);

?

System.out.println(account);

?

sqlSession.close();

?

SqlSessionsqlSession2=sqlSessionFactory.openSession();

?

AccountDaoaccountDao2=sqlSession2.getMapper(AccountDao.class);

?

accountDao2.queryAccountById(1);

?

sqlSession.close();

?

}

效果:

CacheHitRatio[com.xxx.dao.AccountDao]:0.5

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

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