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