很逆天的一件事是,我上一次發(fā)mybatis是在2022年10月15號,然后直到今天才開始總結(jié)下一篇Mybatis的東西。一年里面忙成那啥了,而且重心都投入在了Elasticsearch的學(xué)習(xí)上面,基本一年下來都在搞ES,并且考下了ECE認(rèn)證,后續(xù)如果有時間,一直想寫一些es學(xué)習(xí)的總結(jié),分享一下。
現(xiàn)在貌似又要開始往管理崗位上面湊熱鬧了,以后可能會有多的時間來總結(jié)之前的一些學(xué)習(xí)了,這個不知道是好事還是壞事,這里就當(dāng)抱怨抱怨。哈哈。
一、緩存的意義
話不多說,我們來看Mybatis中的緩存,我們之前說過緩存,那還是在2022年的時候,我們知道m(xù)ybatis作為ORM框架,他的核心任務(wù)就是和數(shù)據(jù)庫打交道。
我們又知道你和數(shù)據(jù)庫打交道我們不說各家數(shù)據(jù)庫對于文件系統(tǒng)的實現(xiàn)是不是自身自帶緩存,他總歸是要和磁盤打交道的,涉及到很重的磁盤IO操作(別抬杠,別和我說硬件的進化,我說的是相對很重)。
而我們面對這種一般就是在業(yè)務(wù)設(shè)計的時候都會想到緩存,是的常見的就是Redis緩存,我們會把數(shù)據(jù)庫(磁盤中)的一部分?jǐn)?shù)據(jù)預(yù)先加載在緩存中,而緩存是內(nèi)存角度的操作,在內(nèi)存中操作數(shù)據(jù)是很輕量級的。以后我們每次讀數(shù)據(jù)庫的時候就在緩存里面放一份,下次再讀的時候就直接走緩存了,那么是不是就能提高請求的響應(yīng),提高業(yè)務(wù)的吞吐。
緩存中的數(shù)據(jù)在修改的時候也不需要直接就刷回磁盤,我們可以在一定時間頻率內(nèi)進行數(shù)據(jù)的寫磁盤,這樣對于你系統(tǒng)的吞吐也是很有提升的,當(dāng)然前提是你能容忍一定的數(shù)據(jù)丟失問題。這些大概就是緩存的意義,但是實現(xiàn)起來卻并不容易,數(shù)據(jù)的一致性,數(shù)據(jù)的持久化,緩存的大小導(dǎo)致的數(shù)據(jù)換出,內(nèi)存中存儲的大小,這都是我們在設(shè)計的時候需要考慮的,如果你想知道一些擴展的東西,可以去看redis的實現(xiàn)。這里不多展開說了。
既然知道了緩存的大致意義,他可以做到減少和磁盤IO的交互,轉(zhuǎn)為和內(nèi)存的交互,這樣就能減少我們的性能開支。那么mybatis是和數(shù)據(jù)庫打交道的,那么他是不是可以加入關(guān)于緩存的設(shè)計呢,答案是可以,他確實這么設(shè)計了一套接口API,并且以接口的形式暴露出來,方面我們自己進行擴展,你可以很方便的就擴展為你們自己的redis架構(gòu)。
二、Mybatis中緩存的設(shè)計
mybatis中緩存是設(shè)計為接口的形式,方便后面的擴展和實現(xiàn)?,F(xiàn)在我們就來看一下這個擴展接口。
這個接口的位置是org.apache.ibatis.cache.Cache類,我們可以看到他是ibatis包中的一個接口。
這個接口有諸多的方法,我們可以借助idea的ctrl+F12組合鍵來看一下。
我們看到他作為緩存的頂層接口,實際上就約定了緩存操作的一些基礎(chǔ)操作,看了一下也其實就那么回事吧。無外乎就是怎么把數(shù)據(jù)放入緩存(putObject),怎么把數(shù)據(jù)從緩存里面取出來(getObject),怎么把數(shù)據(jù)從緩存中移除出去(removeObject),怎么清除緩存(clear),其他的基本也是圍繞這幾個操作展開的一些更加細(xì)化的操作,我們說其實不用看他這個代碼也能想到他就是這么些個功能。
原諒我這里再次吐槽一下,mybatis的源碼注釋,真的少。我的建議是要不干脆別加了。
這里需要額外說一件事就是那個getReadWriteLock這個方法,看著意思是獲取讀寫鎖,但是我們看一下源碼:
/**
* 從3.2.6開始,mybatis已經(jīng)移除了這個方法,所以其實他不重要。
* Optional. As of 3.2.6 this method is no longer called by the core.
*
* 以后的鎖需要由提供緩存的人來維護這個并發(fā)安全,所以如果你怕麻煩,那我推薦你使用redis,服務(wù)端寫入單線程,沒有這個苦惱,我們一直都用它。
* Any locking needed by the cache must be provided internally by the cache provider.
*
* @return A ReadWriteLock
*/
default ReadWriteLock getReadWriteLock() {
return null;
}
那按照我們對于緩存的理解,自然你往緩存里面放數(shù)據(jù)的時候,就要指定數(shù)據(jù)的key,方便我們后面去根據(jù)這個key去取,自然putObject的參數(shù)就有兩個,一個是標(biāo)識key,一個是緩存的數(shù)據(jù)。
那我們?nèi)?shù)據(jù)的時候自然就是根據(jù)這個標(biāo)識去取,自然getObject的參數(shù)就是那個key.
移除數(shù)據(jù)和獲取數(shù)據(jù)其實是一樣的,都是找到這個數(shù)據(jù)。這個其實就類似于map,而緩存實際上也就是map的那個意思,可以一起理解一下。
clear不說了,屬于AOE大招,直接全刪,自然也不需要區(qū)分什么誰是誰,不要參數(shù),直接全干掉。
至此我們是對于這個緩存有個大概的理解了。下面我們就來實際操作一下,我們看看咋用。親愛的。
三、先讓你來設(shè)計一下緩存
既然mybatis這么貼心為我們設(shè)計了緩存接口,我們只需要實現(xiàn)一下就能實現(xiàn)緩存功能。那你不試一把,實在是對不起遠(yuǎn)在大洋彼岸的那些大佬們(或者你用plus,那就稍微對得起他們了)。
來吧,我們來試試。試試就逝逝。
3.1、實現(xiàn)接口
第一步、我們先定義一個org.apache.ibatis.cache.Cache接口的接口實現(xiàn)類,就叫他IkunCache吧。
public class IkunCache implements Cache {
@Override
public String getId() {
return null;
}
@Override
public void putObject(Object key, Object value) {
}
@Override
public Object getObject(Object key) {
return null;
}
@Override
public Object removeObject(Object key) {
return null;
}
@Override
public void clear() {
}
@Override
public int getSize() {
return 0;
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
}
好了,我們現(xiàn)在已經(jīng)完成了接口實現(xiàn)類的工作了,下一步我們要考慮一下既然是緩存,那么數(shù)據(jù)到底緩存到哪呢,redis?一上來就開大,不太好吧(實際上我本地沒有redis環(huán)境)。我們先放在我們的本地內(nèi)存試試。
3.2、制定存儲結(jié)構(gòu)
既然要在本地存,那作為java boy,本地其實就那么幾種結(jié)構(gòu),存那么多緩存數(shù)據(jù)你高低得是個集合吧,那無外乎就是List,Set,Map這種,list和set不行,我直接否了,因為他們獲取數(shù)據(jù)需要遍歷。移除數(shù)據(jù)也要遍歷。我用緩存是為了快,你給我來個遍歷,你腦子和我腦子到底誰有問題。
所以我們就把目標(biāo)定在了Map上,簡單點,哥們,直接用HashMap,能通過key直接獲取刪除,等會你說key,你剛才說了key。還記不記得我們說緩存redis這種的時候,就是Key,我們又說Cache接口里面那些方法的時候也說的是key。好了,HashMap,我宣布,你就是今天的天命之子。
于是我們就要聲明一個本地變量,就是HashMap,然后我們不管是寫緩存,還是讀緩存,還是清空緩存都用這個map就好了,那么代碼就進化成為這樣。
public class IkunCache implements Cache {
// 緩存內(nèi)容的容器
private static Map<Object,Object> ikunCache = new HashMap<>();
/**
因為整個應(yīng)用中可能存在多個緩存,比如緩存的redis,本地的,或者按照業(yè)務(wù)劃分緩存的訂單的,用戶的等等
這里可以為你的緩存指定一個名字來區(qū)分
**/
@Override
public String getId() {
// 我們用緩存類的類名來區(qū)分開每個緩存實例,每個緩存類都維護自己的一個map即可,這樣就區(qū)分開了,簡單設(shè)計一下,別太糾結(jié)
return getClass().getName();
}
@Override
public void putObject(Object key, Object value) {
// 把數(shù)據(jù)放入緩存
ikunCache.put(key,value);
}
@Override
public Object getObject(Object key) {
// 從緩存中獲取數(shù)據(jù)
return ikunCache.get(key);
}
@Override
public Object removeObject(Object key) {
// 從緩存中移除數(shù)據(jù)
return ikunCache.remove(key);
}
@Override
public void clear() {
// 開大招,清空緩存
ikunCache.clear();
}
@Override
public int getSize() {
// 獲取緩存的長度
return ikunCache.size();
}
@Override
public ReadWriteLock getReadWriteLock() {
// 移除了
return null;
}
}
那現(xiàn)在是不是你就實現(xiàn)了這個緩存接口了,你就能用了。至于你是不是還有其他的實現(xiàn)類,那可以制定多個實現(xiàn)類。你自己多實現(xiàn)幾個試試,你可以給你的map整個過期時間等等,或者換成別的容器,到時候用那個看你指定哪個就好?;蛘吣阒苯訐Q成redis,全部放redis里面,把你的k-v序列化好就行。
原則就是能用就行,畢竟我們的路線是,完成,完善,完美,完蛋。
這是我們的簡單實現(xiàn),那么mybatis實際上提供了很多內(nèi)置的實現(xiàn)方式,也就是他自己有很多實現(xiàn)類,我們就來看一下。
四、Mybatis內(nèi)置緩存實現(xiàn)
我們看一下Mybatis自己內(nèi)置的實現(xiàn)類。
這個圖好像被壓縮了,我來用文字列出這幾個類。
Cache (org.apache.ibatis.cache):這是那個緩存接口
SoftCache (org.apache.ibatis.cache.decorators)
PerpetualCache (org.apache.ibatis.cache.impl)
LoggingCache (org.apache.ibatis.cache.decorators)
SynchronizedCache (org.apache.ibatis.cache.decorators)
ScheduledCache (org.apache.ibatis.cache.decorators)
LruCache (org.apache.ibatis.cache.decorators)
IkunCache (com.yx.cache) :這是我們剛才自己實現(xiàn)的,就不說了。
WeakCache (org.apache.ibatis.cache.decorators)
FifoCache (org.apache.ibatis.cache.decorators)
SerializedCache (org.apache.ibatis.cache.decorators)
BlockingCache (org.apache.ibatis.cache.decorators)
TransactionalCache (org.apache.ibatis.cache.decorators)
我們看到除了我們自己實現(xiàn)的,mybatis自己內(nèi)置的幾個類貌似分為兩種。
一種是屬于org.apache.ibatis.cache.decorators包下面的,我們說decorators其實翻譯一下就是裝飾器
裝飾器這三個字有沒有喚醒你內(nèi)心的設(shè)計模式之魂,所以我們知道這些類都是裝飾器門面,
真正工作的其實不在他這里,其核心實際在下面這個PerpetualCache。
那我們說裝飾器模式的作用是裝飾器為了給你的目標(biāo)類增強功能。
第二種是屬于org.apache.ibatis.cache.impl包下面的,其實這個包下面就一個PerpetualCache。
根據(jù)我們對于裝飾器的理解,所以其實他的目標(biāo)類是在PerpetualCache,其余的都是給他增強功能用的,所以我們直接看PerpetualCache就可以了。
4.1、干活的其實是我,目標(biāo)PerpetualCache登場
我們來看一下PerpetualCache的源碼。
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
再吐槽一次,你們是真不寫注釋啊哥,我們看到他除了傳入一個id來指定緩存的名字來區(qū)分以外,其余的實現(xiàn)也是用了一個HashMap作為緩存容器,除了他重寫了equal和hashcode方法,其余的和我們剛才的實現(xiàn)一毛一樣。你看看,其實你也是大佬。
但是大佬肯定不能這么粗糙,于是就有了那么多的裝飾器,來增強你這個無比粗糙的緩存。
4.2、我來給你加點buff吧,緩存裝飾器登場
我們看到了那個緩存干活的類,PerpetualCache,主打一個粗糙,那mybats絕對不會這么low,于是他借助裝飾器模式來實現(xiàn)了一組增強功能的裝飾器。來看一下。
LruCache (org.apache.ibatis.cache.decorators)
FifoCache (org.apache.ibatis.cache.decorators)
SoftCache (org.apache.ibatis.cache.decorators)
LoggingCache (org.apache.ibatis.cache.decorators)
SynchronizedCache (org.apache.ibatis.cache.decorators)
ScheduledCache (org.apache.ibatis.cache.decorators)
WeakCache (org.apache.ibatis.cache.decorators)
SerializedCache (org.apache.ibatis.cache.decorators)
BlockingCache (org.apache.ibatis.cache.decorators)
TransactionalCache (org.apache.ibatis.cache.decorators)
你看到了,我把LruCache和FifoCache單獨列在了一起,和我一樣聰明的你一眼就看懂了,Lru和Fifo其實是一種淘汰機制,在內(nèi)存頁置換的時候?qū)W習(xí)的時候,你是知道的,我不多說了。算了簡單說一下。
LRU:Least Recently Used,最近最少使用,換言之就是最近用的最少的數(shù)據(jù),就先淘汰。
FIFO:First In First Out,先進先出,我不管你誰用的多,那么花里花哨沒用,誰先進來誰先滾,呆那么長時間干嘛。
以上都是在緩存中的數(shù)據(jù)超過了限制的時候,再有新的數(shù)據(jù)進來的時候,需要淘汰掉老的緩存數(shù)據(jù)的時候設(shè)計的策略。可見mybatis是實現(xiàn)了這兩種的。
LoggingCache (org.apache.ibatis.cache.decorators):增強日志打印的功能
BlockingCache (org.apache.ibatis.cache.decorators):阻塞增強,保證一時間只有一個線程讀寫緩存,線程安全得以保證。
ScheduledCache (org.apache.ibatis.cache.decorators):能夠定時自動刷新緩存,按照一定的時間定時去清空緩存。你可以在代碼設(shè)置這個時間間隔。
SerializedCache (org.apache.ibatis.cache.decorators):可以自動幫我們完成k-v的序列化和反序列化,序列化方式就是jdk的Serializedble序列化方式。
TransactionalCache (org.apache.ibatis.cache.decorators):這個裝飾器只有在事務(wù)操作成功的時候才會寫入緩存,你是不是發(fā)現(xiàn)這個保證了寫入DB和緩存的一致性,不至于事務(wù)失敗了,緩存寫進去了,挺好的是不是。
其余的不常用就不說了。其實主要是加裝飾器是為了擴展功能,但是實際上我們一般也不咋用,默認(rèn)就可以了,如果你需要設(shè)計的更加精細(xì)可以考慮使用。我們一般還是借助redis去實現(xiàn)分布式緩存。這里的學(xué)習(xí)是為了學(xué)習(xí)他的設(shè)計。
至于這個裝飾器,你說你不會用,你知道的,我會。
4.2.1、緩存裝飾器簡單操作
我們來寫個簡單的實現(xiàn)看看裝飾器模式怎么用。
@Test
public void testMyCache() throws IOException {
// 我們先聲明一個PerpetualCache 被增強的目標(biāo)緩存對象
PerpetualCache perpetualCache = new PerpetualCache("levi");
// 然后創(chuàng)建加buff的裝飾器緩存類,把我們要增強的傳入,此時的cache 就是一個有了FIFO的能力緩存了
FifoCache cache = new FifoCache(perpetualCache);
// 我們設(shè)計緩存大小為5來模擬緩存換出,你看看,增強之后都有設(shè)置大小的能力了,
cache.setSize(5);
// 放入五個對象,此時已經(jīng)滿了
for (int i = 0; i < 5; i++) {
cache.putObject(i, i);
}
// 輸出一下第一個放入的key就是0的對象
System.out.println(cache.getObject(0));
// 再放一個進去,發(fā)生換出
cache.putObject(5, 5);
// 再取一下第一個放進去的,不出意外的話,他已經(jīng)沒了,因為他第一個放進去,
// 按照FIFO的邏輯,他被淘汰換出了
System.out.println(cache.getObject(0));
}
不出意外的話,果然沒有意外,輸出符合預(yù)期。
0
null
4.2.2、緩存裝飾器連環(huán)設(shè)置
我們上面就設(shè)置了一個增強,其實他可以多個增強,增強一組能力。寫法如下。
@Test
public void testMyCache2() throws IOException {
PerpetualCache perpetualCache = new PerpetualCache("levi");
FifoCache fifoCache = new FifoCache(perpetualCache);
LoggingCache loggingCache = new LoggingCache(perpetualCache);
}
這樣他就增強了換出和日志打出的功能,你也可以再加。
或者是鏈?zhǔn)降膶懛ā?/p>
@Test
public void testMyCache3() throws IOException {
PerpetualCache perpetualCache = new PerpetualCache("levi");
FifoCache fifoCache = new FifoCache(perpetualCache);
LoggingCache cache = new LoggingCache(fifoCache);
}
這樣都可以。至于什么是裝飾器的設(shè)計,我會在設(shè)計模式寫出來。
4.3、貼心的源碼
你肯定會問我為啥知道這個裝飾器怎么用,首先我告訴你我學(xué)過設(shè)計模式,那么沒學(xué)過的兄弟們就不配用嗎,mybatis此時從天而降,大喊一聲,放開那個姑娘,讓我來。
我們看一下mybatis源碼有一個test包。這里都有,你照著看就行了,哥們,不難吧。
五、裝飾器模式和代理模式的區(qū)別
5.1、核心區(qū)別
為啥要提到這兩個模式呢,因為我們前面說代理模式的時候,提到過。
代理設(shè)計模式:為邏輯添加功能。增強功能。
裝飾器模式:現(xiàn)在我們又說裝飾器模式也是增強功能。
而且二者的URL設(shè)計類圖都大差不差,那有啥區(qū)別的,用的時候有啥分別呢?
代理模式是為了增強的核心邏輯的額外功能。附加功能。
裝飾器模式是為了增強核心邏輯的核心功能。
舉個例子,我們的service的事務(wù)就是代理模式做的,那你service可能是去查用戶,或者登錄等等,肯定不是做的事務(wù),事務(wù)是額外加的功能,增強的功能不是你要做的事,你本來是登錄,他給你增強了事務(wù)功能,是兩件事。這就是代理模式。
而我們上面的緩存裝飾器增強的是緩存的換出和日志,這就是核心功能的增強,增強的是自己的功能,是一件事。這就是他們的區(qū)別。
可能你又要問了,既然都差不多,我為啥要聽他呢,我用裝飾器模式增強額外功能不行嗎?
答案是行,就你行,誰能難得到你啊,數(shù)你能了。
那么大哥,你的代碼以后還有人維護呢,你自己不按規(guī)矩來,后面的兄弟是不是會不理解,這樣混雜一起,代碼只會越來越爛,屎上雕花。
5.2、語法區(qū)別
裝飾器代碼我們剛才看到了,我們能套娃式的鏈?zhǔn)皆鰪姟?br> 但是代理模式好像沒這么寫。
實際這個理由不充分,因為代理一般就增強一層,實際上他也能多個套,看你寫不寫了。
// 動態(tài)代理的InvocationHandler接口實現(xiàn)
InvocationHandler invocationHandler = new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 這里你可以繼續(xù)對prox這個代理進行增強套娃,再增強一次,只不過不這么做而已
Object result = method.invoke(userService, args);
return result;
}
};
5.3、無中生有
動態(tài)代理能無中生有,產(chǎn)生新對象。就像mybatis中生成DAO的實現(xiàn)類。
裝飾器模式不行。文章來源:http://www.zghlxwxcb.cn/news/detail-801956.html
實際上那23種模式,你也用不全,有的甚至不能在javaweb中硬套進去。所以多思考設(shè)計,多用設(shè)計,多踩坑,踩坑了再改,才能理解的更深刻,設(shè)計模式就這樣,看是看不會的,親。文章來源地址http://www.zghlxwxcb.cn/news/detail-801956.html
到了這里,關(guān)于橘子學(xué)Mybatis07之Mybatis關(guān)于緩存的設(shè)計的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!