關(guān)于 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í)緩存查詢數(shù)據(jù),如果二級(jí)緩存有則從二級(jí)緩存中獲取數(shù)據(jù),如果二級(jí)緩存沒(méi)有,從一級(jí)緩存找是否有緩存數(shù)據(jù),如果一級(jí)緩存沒(méi)有,查詢數(shù)據(jù)庫(kù)。

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

mybatis 二級(jí)緩存對(duì)細(xì)粒度的數(shù)據(jù)級(jí)別的緩存實(shí)現(xiàn)不好,對(duì)同時(shí)緩存較多條數(shù)據(jù)的緩存,比如如下需求:對(duì)商品信息進(jìn)行緩存,由于商品信息查詢?cè)L問(wè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 查詢,第二次不再?gòu)臄?shù)據(jù)庫(kù)查詢。

原理:一級(jí)緩存采用 Hashmap 存儲(chǔ),mybatis 執(zhí)行查詢時(shí),從緩存中查詢,如果緩存中沒(méi)有從數(shù)據(jù)庫(kù)查詢。如果該?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

b.刷新緩存

Session 提交此時(shí)緩存數(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);

}

效果:

5. 二級(jí)緩存

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

使用場(chǎng)景

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

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

全局文件配置(mybatis.xml)

<setting name="cacheEnabled" value="true"/>?Mapper.xml 中加入 :打開(kāi)該 mapper 的二級(jí)緩存?


<cache/>

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

<cache

eviction="FIFO"??

flushInterval="60000" ?

size="512" ?

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ì)象必須支持序列化

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>

刷新二級(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?

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();?

}

效果:

擴(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 依賴

<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(不是必須的沒(méi)有使用默認(rèn)配置)


<ehcache xmlns: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)失效線程運(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(較少使用)?

-->?

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

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


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

maxElementsOnDisk="10" diskPersistent="true"

diskExpiryThreadIntervalSeconds="300"?

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

-->

測(cè)試:

@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)容合作請(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)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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