導語
看着這篇文章的你一定是程序員了吧,哈哈,這麼快來添加小編的微信,帶你進入Java技術交流羣;備註csdn,羣裏的大佬,等你來聊;
小編微信:372787553
Mybatis 自定義二級緩存
Mybatis 在日常的Java開發,應該非常廣泛,這裏我就不過多介紹,今天我們聊聊Mybatis 的二級緩存,在日常開發中,如果經常訪問數據庫,開銷 和速度都是個問題,Mybatis爲我們提供了二級緩存,但是Mybatis自帶的二級緩存,存在一些缺陷,因爲他是本地,這樣我們部署多個實例就會發生一些髒讀/幻讀的問題(因爲是多機器部署,緩存無法統一處理而造成的,如果您的服務是單機部署,不會產生這樣的問題)
1.自帶緩存
MyBatis 內置了一個強大的事務性查詢緩存機制,它可以非常方便地配置和定製。 爲了使它更加強大而且易於配置,我們對 MyBatis 3 中的緩存實現進行了許多改進。
默認情況下,只啓用了本地的會話緩存,它僅僅對一個會話中的數據進行緩存。 要啓用全局的二級緩存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
基本上就是這樣。這個簡單語句的效果如下:
- 映射語句文件中的所有 select 語句的結果將會被緩存。
- 映射語句文件中的所有 insert、update 和 delete 語句會刷新緩存。
- 緩存會使用最近最少使用算法(LRU, Least Recently Used)算法來清除不需要的緩存。
- 緩存不會定時進行刷新(也就是說,沒有刷新間隔)。
- 緩存會保存列表或對象(無論查詢方法返回哪種)的 1024 個引用。
- 緩存會被視爲讀/寫緩存,這意味着獲取到的對象並不是共享的,可以安全地被調用者修改,而不干擾其他調用者或線程所做的潛在修改。
提示 緩存只作用於 cache 標籤所在的映射文件中的語句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的語句將不會被默認緩存。你需要使用 @CacheNamespaceRef 註解指定緩存作用域。
這些屬性可以通過 cache 元素的屬性來修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
這個更高級的配置創建了一個 FIFO 緩存,每隔 60 秒刷新,最多可以存儲結果對象或列表的 512 個引用,而且返回的對象被認爲是隻讀的,因此對它們進行修改可能會在不同線程中的調用者產生衝突。
可用的清除策略有:
LRU
– 最近最少使用:移除最長時間不被使用的對象。FIFO
– 先進先出:按對象進入緩存的順序來移除它們。SOFT
– 軟引用:基於垃圾回收器狀態和軟引用規則移除對象。WEAK
– 弱引用:更積極地基於垃圾收集器狀態和弱引用規則移除對象。
默認的清除策略是 LRU。
flushInterval(刷新間隔)屬性可以被設置爲任意的正整數,設置的值應該是一個以毫秒爲單位的合理時間量。 默認情況是不設置,也就是沒有刷新間隔,緩存僅僅會在調用語句時刷新。
size(引用數目)屬性可以被設置爲任意正整數,要注意欲緩存對象的大小和運行環境中可用的內存資源。默認值是 1024。
readOnly(只讀)屬性可以被設置爲 true 或 false。只讀的緩存會給所有調用者返回緩存對象的相同實例。 因此這些對象不能被修改。這就提供了可觀的性能提升。而可讀寫的緩存會(通過序列化)返回緩存對象的拷貝。 速度上會慢一些,但是更安全,因此默認值是 false。
提示 二級緩存是事務性的。這意味着,當 SqlSession 完成並提交時,或是完成並回滾,但沒有執行 flushCache=true 的 insert/delete/update 語句時,緩存會獲得更新。
2. 自定義緩存
這裏的自定義緩存我們採用Redis來進行持久化,這是我們需要更改配置:
<cache type="com.javayh.mybatis.cache.RedisCache"/>
這個示例展示瞭如何使用一個自定義的緩存實現。type 屬性指定的類必須實現 org.apache.ibatis.cache.Cache 接口,且提供一個接受 String 參數作爲 id 的構造器。 這個接口是 MyBatis 框架中許多複雜的接口之一,但是行爲卻非常簡單。
PerpetualCache
類時Mybatis爲我們提供的自帶的二級緩存實現
2.1 Redis版本實現
public class RedisCache implements Cache {
private String id;
/** 讀寫鎖*/
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
public RedisCache() {
}
public RedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Autowired
private RedisUtil redisUtil;
private static RedisCache redisCache ;
@PostConstruct
public void init() {
redisCache = this;
redisCache.redisUtil = this.redisUtil;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
if (value != null) {
//向Redis中添加數據,有效時間是12小時
redisCache.redisUtil.setObj(key.toString(),value,43200);
log.debug(value.toString());
}
}
@Override
public Object getObject(Object key) {
try {
if (key != null) {
return redisCache.redisUtil.get(key.toString());
}
} catch (Exception e) {
Log.error("Mybatis Get Cache",e.getStackTrace());
}
return null;
}
@Override
public Object removeObject(Object key) {
try {
if (!ObjectUtils.isEmpty(key)) {
redisCache.redisUtil.del(key.toString());
}
} catch (Exception e) {
Log.error("Mybatis Del Cache",e.getStackTrace());
}
return null;
}
@Override
public void clear() {
try {
Set<String> keys = redisCache.redisUtil.keys(this.id);
if (!CollectionUtils.isEmpty(keys)) {
redisCache.redisUtil.del(keys);
}
} catch (Exception e) {
Log.error("Mybatis Clear Cache",e.getStackTrace());
}
}
@Override
public int getSize() {
return redisCache.redisUtil.execute();
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
2.2 測試驗證
第一次查詢:
第二次查詢:
這是我們發現,第二次查詢已經命中緩存,並且查詢的速度大大提升了,進而達到了對服務優化;並且也避免了多機部署帶來的問題;
這時還有一個問題,細心的朋友也許已經發現,雖然我們對緩存進行過期時間的設置,但是這期間我們對數據進行增刪改的操作,還是查詢緩存,問題好像更大;其實不然,我們對數據進行刪除時,Mybatis會自動刪除緩存;
刪除一條數據
再次查詢
如上圖我們發現,當對數據進行Update時,會進行緩存的銷燬
提示 上一節中對緩存的配置(如清除策略、可讀或可讀寫等),不能應用於自定義緩存。
請注意,緩存的配置和緩存實例會被綁定到 SQL 映射文件的命名空間中。 因此,同一命名空間中的所有語句和緩存將通過命名空間綁定在一起。 每條語句可以自定義與緩存交互的方式,或將它們完全排除於緩存之外,這可以通過在每條語句上使用兩個簡單屬性來達成。 默認情況下,語句會這樣來配置:
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
鑑於這是默認行爲,顯然你永遠不應該以這樣的方式顯式配置一條語句。但如果你想改變默認的行爲,只需要設置 flushCache 和 useCache 屬性。比如,某些情況下你可能希望特定 select 語句的結果排除於緩存之外,或希望一條 select 語句清空緩存。類似地,你可能希望某些 update 語句執行時不要刷新緩存。
2.3 cache-ref
回想一下上一節的內容,對某一命名空間的語句,只會使用該命名空間的緩存進行緩存或刷新。 但你可能會想要在多個命名空間中共享相同的緩存配置和實例。要實現這種需求,你可以使用 cache-ref 元素來引用另一個緩存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
項目源代碼:github源代碼地址
演示源代碼:github源代碼地址
常見面試題總結
1.Mybatis 一級緩存/二級緩存命中原則
- sql id
- 查詢參數
- 分頁參數
- sql語句
- 環境
一級緩存前期:SqlSession內
二級緩存前提:SqlSessionactory內
2.Mybatis 一級緩存生命週期
產生:調用查詢語句時
銷燬:Session關閉,Conmit提交,Rolback回滾,Update,ClearCahche
3.Mybatis 二級緩存生命週期
產生:調用查詢語句,並執行了sqlsession.close();
銷燬:Update