緩存實現的方式
-
一級緩存
- 二級緩存
案例實操
1. 一級緩存
基於 PerpetualCache 的 HashMap 本地緩存(mybatis 內部實現 cache 接口),其存儲作用域爲 Session,當 Session flush 或 close 之後,該 Session 中的所有 Cache 就將清空;
2. 二級緩存
一級緩存其機制相同,默認也是採用 PerpetualCache 的 HashMap 存儲,不同在於其存儲作用域爲 Mapper(Namespace),並且可自定義存儲源,如 Ehcache;
對於緩存數據更新機制,當某一個作用域(一級緩存 Session/二級緩存 Namespaces)的進行了 C/R/U/D 操作後,默認該作用域下所有 select 中的緩存將被 clear。
如果二緩存開啓,首先從二級緩存查詢數據,如果二級緩存有則從二級緩存中獲取數據,如果二級緩存沒有,從一級緩存找是否有緩存數據,如果一級緩存沒有,查詢數據庫。
3. 二級緩存侷限性
mybatis 二級緩存對細粒度的數據級別的緩存實現不好,對同時緩存較多條數據的緩存,比如如下需求:對商品信息進行緩存,由於商品信息查詢訪問量大,但是要求用戶每次都能查詢最新的商品信息,此時如果使用 mybatis 的二級緩存就無法實現當一個商品變化時只刷新該商品的緩存信息而不刷新其它商品的信息,因爲 mybaits 的二級緩存區域以 mapper 爲單位劃分,當一個商品信息變化會將所有商品信息的緩存數據全部清空
4. 一級緩存(默認開啓)
Mybatis 默認提供一級緩存,緩存範圍是一個 sqlSession。在同一個 SqlSession 中,兩次執行相同的 sql 查詢,第二次不再從數據庫查詢。
原理:一級緩存採用 Hashmap 存儲,mybatis 執行查詢時,從緩存中查詢,如果緩存中沒有從數據庫查詢。如果該 SqlSession 執行 clearCache() 提交或者增加刪除修改操作,清除緩存。
默認就存在,瞭解觀察結果即可
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 提交此時緩存數據被刷新
@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. 二級緩存
一級緩存是在同一個 sqlSession 中,二級緩存是在同一個 namespace 中,因此相同的 namespace 不同的 sqlsession 可以使用二級緩存。
使用場景
- 對查詢頻率高,變化頻率低的數據建議使用二級緩存。
- 對於訪問多的查詢請求且用戶對查詢結果實時性要求不高,此時可採用 mybatis 二級緩存技術降低數據庫訪問量,提高訪問速度,業務場景比如:耗時較高的統計分析 sql、電話賬單查詢 sql 等。
全局文件配置(mybatis.xml)
<setting name="cacheEnabled" value="true"/>
Mapper.xml 中加入 :打開該 mapper 的二級緩存
<!-- 開啓該 mapper 的二級緩存 -->
<cache/>
cache 標籤常用屬性
<cache
eviction="FIFO" <!--回收策略爲先進先出-->
flushInterval="60000" <!--自動刷新時間 60s-->
size="512" <!--最多緩存 512 個引用對象-->
readOnly="true"/> <!--只讀-->
說明:
- 映射語句文件中的所有 select 語句將會被緩存。
- 映射語句文件中的所有 insert,update 和 delete 語句會刷新緩存。
- 緩存會使用 Least Recently Used(LRU,最近最少使用的)算法來收回。
- 緩存會根據指定的時間間隔來刷新.
- 緩存會存儲 1024 個對象
PO 對象必須支持序列化
public class User implements Serializable {
}
關閉 Mapper 下的具體的 statement 的緩存
使用 useCache:默認爲 true
<select id="findUserByid" parameterType="int" resultType="User"
useCache="false">
SELECT * FROM user WHERE id=#{id}
</select>
刷新二級緩存
操作 CUD 的 statement 時候,會強制刷新二級緩存 即默認 flushCache="true" ,如果想關閉設定爲 flushCache="false"即可 ,不建議關閉刷新,因爲操作更新刪除修改,關閉後容易獲取髒數據。
二級緩存測試:
@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
如果有多條服務器 ,不使用分佈緩存,緩存的數據在各個服務器單獨存儲,不方便系統開發。所以要使用分佈式緩存對緩存數據進行集中管理。因此可是使用 ehcache memcached redis
mybatis 本身來說是無法實現分佈式緩存的,所以要與分佈式緩存框架進行整合。 EhCache 是一個純 Java 的進程內緩存框架,具有快速、精幹等特點;Ehcache 是一種廣泛 使用的開源 Java 分佈式緩存。主要面向通用緩存,Java EE 和輕量級容器。它具有內存和磁盤存儲,緩存加載器,緩存擴展,緩存異常處理程序,一個 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(不是必須的沒有使用默認配置)
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../bin/ehcache.xsd">
<!--
name:Cache 的唯一標識
maxElementsInMemory:內存中最大緩存對象數
maxElementsOnDisk:磁盤中最大緩存對象數,若是 0 表示無窮大
eternal:Element 是否永遠不過期,如果爲 true,則緩存的數據始終有效,如果爲 false
那麼還要根據 timeToIdleSeconds,timeToLiveSeconds 判斷
overflowToDisk:配置此屬性,當內存中 Element 數量達到 maxElementsInMemory 時,
Ehcache 將會 Element 寫到磁盤中
timeToIdleSeconds:設置 Element 在失效前的允許閒置時間。僅當 element 不是永久有效
時使用,可選屬性,默認值是 0,也就是可閒置時間無窮大
timeToLiveSeconds:設置 Element 在失效前允許存活時間。最大時間介於創建時間和失效
時間之間。僅當 element 不是永久有效時使用,默認是 0.,也就是 element 存活時間無窮
大
diskPersistent:是否緩存虛擬機重啓期數據
diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是 120 秒
diskSpoolBufferSizeMB:這個參數設置 DiskStore(磁盤緩存)的緩存區大小。默認是
30MB。每個 Cache 都應該有自己的一個緩衝區
memoryStoreEvictionPolicy:當達到 maxElementsInMemory 限制時,Ehcache 將會根據
指定的策略去清理內存。默認策略是 LRU(最近最少使用)。你可以設置爲 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