整合SpringCache與EhCache實現(xiàn)本地緩存

一.EhCache和SpringCache簡介

1.1 EhCache

EhCache 是一個純 Java 的進程內(nèi)緩存框架,具有快速惠猿、精干等特點幅骄,是 Hibernate 中默認CacheProvider。Ehcache 是一種廣泛使用的開源 Java 分布式緩存矾麻。主要面向通用緩存,Java EE 和輕量級容器姑食。它具有內(nèi)存和磁盤存儲波岛,緩存加載器,緩存擴展,緩存異常處理程序,一個 gzip 緩存 servlet 過濾器,支持 REST 和 SOAP api 等特點。
EhCache 適用于單體應用的緩存音半,當應用進行分布式部署的時候盆色,各應用的副本之間緩存是不同步的。EhCache 由于沒有獨立的部署服務祟剔,所以它的緩存和應用的內(nèi)存是耦合在一起的隔躲,當緩存數(shù)據(jù)量比較大的時候要注意系統(tǒng)資源能不能滿足應用內(nèi)存的要求。

1.2 SpringCache

Spring Cache作為緩存框架的門面物延,之所以說它是門面宣旱,是因為它只提供接口層的定義以及AOP注解等,不提供緩存的具體存取操作叛薯。緩存的具體存儲還需要具體的緩存存儲浑吟,比如EhCache 、Redis等耗溜。Spring Cache與緩存框架的關(guān)系有點像SLF4j與logback组力、log4j的關(guān)系。
Spring Cache通過注解的方式來操作緩存抖拴,一定程度上減少了程序員緩存操作代碼編寫量燎字。注解添加和移除都很方便,不與業(yè)務代碼耦合阿宅,容易維護候衍。

二.整合Spring Cache與EhCache

2.1 pom.xml添加SpringCache和Ehcache的jar依賴

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache</artifactId>
    </dependency>

2.2 添加入口啟動類@EnableCaching注解開啟Caching

@EnableCaching

在Spring Boot中通過@EnableCaching注解自動化配置合適的緩存管理器(CacheManager),Spring Boot根據(jù)下面的順序去偵測緩存提供者洒放,也就是說Spring Cache支持下面的這些緩存框架:

  • Generic
  • JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
  • EhCache 2.x(發(fā)現(xiàn)ehcache的bean蛉鹿,就使用ehcache作為緩存
  • Hazelcast
  • Infinispan
  • Couchbase
  • Redis
  • Caffeine
  • Simple

2.3添加ehcache配置

spring:
  cache:
    type: ehcache
    ehcache:
      config: classpath:/ehcache.xml

config:classpath:/ehcache.xml可以不用寫,因為默認就是這個路徑往湿。但ehcache.xml必須有妖异。
在 resources 目錄下,添加 ehcache 的配置文件 ehcache.xml 领追,文件內(nèi)容如下:

<ehcache>
    <diskStore path="java.io.tmpdir/cache_dongbb"/>
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />
    <cache name="article"
            maxElementsInMemory="10000"
            eternal="true"
            overflowToDisk="true"
            diskPersistent="true"
            diskExpiryThreadIntervalSeconds="600"/>
</ehcache>

配置含義:

  1. name:緩存名稱他膳,與緩存注解的value(cacheNames)屬性值相同。
  2. maxElementsInMemory:緩存最大個數(shù)蔓腐。
  3. eternal:緩存對象是否永久有效矩乐,一但設置了龄句,timeout將不起作用回论。
  4. timeToIdleSeconds:設置對象在失效前的允許閑置時間(單位:秒)散罕。僅當eternal=false對象不是永久有效時使用,可選屬性傀蓉,默認值是0欧漱,也就是可閑置時間無窮大。
  5. timeToLiveSeconds:設置對象在失效前允許存活時間(單位:秒)葬燎。最大時間介于創(chuàng)建時間和失效時間之間误甚。僅當eternal=false對象不是永久有效時使用,默認是0.谱净,也就是對象存活時間無窮大窑邦。
  6. overflowToDisk:當內(nèi)存中對象數(shù)量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中壕探。
  7. diskSpoolBufferSizeMB:這個參數(shù)設置DiskStore(磁盤緩存)的緩存區(qū)大小冈钦。默認是30MB。每個Cache都應該有自己的一個緩沖區(qū)李请。
  8. maxElementsOnDisk:硬盤最大緩存?zhèn)€數(shù)瞧筛。
  9. diskPersistent:是否緩存虛擬機重啟期數(shù)據(jù)。
  10. diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔导盅,默認是120秒较幌。
  11. memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據(jù)指定的策略去清理內(nèi)存白翻。默認策略是LRU(最近最少使用)乍炉。你可以設置為FIFO(先進先出)或是LFU(較少使用)。
  12. clearOnFlush:內(nèi)存數(shù)量最大時是否清除滤馍。
  13. diskStore 則表示臨時緩存的硬盤目錄恩急。

三. 緩存的使用方法

3.1 緩存注解-增刪改查

  • @Cacheable:針對查詢方法配置,能夠根據(jù)查詢方法的請求參數(shù)對其結(jié)果進行緩存
  • @CacheEvict:被注解的方法執(zhí)行前或者執(zhí)行之后纪蜒,刪除緩存
  • @CachePut:調(diào)用被注解的方法衷恭,對其返回結(jié)果進行緩存更新
  • @Caching:可以將上面三種注解,組合起來使用

3.2 單個對象的查詢緩存

@Cacheable注解的方法纯续,在第一次被請求的時候執(zhí)行方法體随珠,并將方法的返回值放入緩存。在第二次請求的時候猬错,由于緩存中已經(jīng)包含該數(shù)據(jù)窗看,將不執(zhí)行被注解的方法的方法體,直接從緩存中獲取數(shù)據(jù)倦炒。對于查詢過程的緩存操作显沈,要滿足上圖中的藍色箭頭線指引的操作流程,所有的操作流程只需要加上一個@Cacheable就可以實現(xiàn)。

public static final String CACHE_OBJECT = "article";  //緩存名稱

@Cacheable(value = CACHE_OBJECT,key = "#id")   //這里的value和key參考下面的redis數(shù)據(jù)庫截圖理解
public ArticleVO getArticle(Long id) {
  return dozerMapper.map(articleMapper.selectById(id),ArticleVO.class);
}

需要注意的是:緩存注解的key是一個SPEL表達式拉讯,“#id”表示獲取函數(shù)的參數(shù)id的值作為緩存的key值涤浇。如果參數(shù)id=1,那么最終redis緩存的key就是:“article::1”魔慷。

3.3 集合對象的查詢緩存

大家要注意ObjectList<Object>是兩種不同的業(yè)務數(shù)據(jù)只锭,所以對應的緩存也是兩種緩存。注意下文中院尔,緩存注解的key是字符串list蜻展,因為緩存注解默認使用SPEL表達式,如果我們想使用字符串需要加上斜杠邀摆。

public static final String CACHE_LIST_KEY  = "\"list\"";

@Cacheable(value = CACHE_OBJECT,key = CACHE_LIST_KEY)
public List<ArticleVO> getAll() {
  List<Article> articles = articleMapper.selectList(null);
  return DozerUtils.mapList(articles,ArticleVO.class);
}

對于查詢過程的緩存操作纵顾,要滿足上圖中的藍色箭頭線指引的操作流程,所有的操作流程只需要加上一個@Cacheable就可以實現(xiàn)栋盹。目前MySQL數(shù)據(jù)庫的article表有4條數(shù)據(jù)片挂,所以緩存結(jié)果是一個包含4個article元素的數(shù)組

3.4 刪除單個對象及其緩存

如下面的代碼所示,將在函數(shù)執(zhí)行成功之后刪除緩存 key為“article::1”的緩存(假設刪除id=1的記錄)贞盯。

@Override
@Caching(evict = {
        @CacheEvict(value = CACHE_OBJECT,key = CACHE_LIST_KEY),   //刪除List集合緩存
        @CacheEvict(value = CACHE_OBJECT,key = "#id")  //刪除單條記錄緩存
})
public void deleteArticle(Long id) {
  articleMapper.deleteById(id);
}
  • 執(zhí)行該方法傳遞參數(shù)id=1音念,先執(zhí)行方法去操作刪除MySQL數(shù)據(jù);成功之后將key為“article::1”的緩存也將被刪除躏敢。
  • 任何一個article記錄被刪除闷愤,都會引起article::list緩存與MySQL數(shù)據(jù)庫記錄不一致的情況,所以需要把article::list集合緩存也刪除掉件余。
  • 因為Java 語法不允許在同一個方法上使用兩個同樣的注解@CacheEvict讥脐,所以我們用@Caching注解把兩個@CacheEvict包起來。

3.5 新增一個對象

  • 新增MySQL數(shù)據(jù)的時候新增緩存么啼器?不是的旬渠,緩存是在獲得查詢結(jié)果時候回寫到緩存里面的,不在新增的時候加緩存端壳。
  • 新增的時候刪除緩存么“告丢?是的,因為我們緩存了List的集合损谦,一旦新增一條記錄岖免。原來MySQL數(shù)據(jù)庫有4條記錄,新增之后MySQL數(shù)據(jù)庫有5條記錄照捡,緩存緩存數(shù)據(jù)庫緩存結(jié)果”article::list“仍然有4條記錄颅湘。緩存中的數(shù)據(jù)與MYSQL數(shù)據(jù)庫中的數(shù)據(jù)不一致,所以把”article::list“緩存刪掉栗精。
@CacheEvict(value = CACHE_OBJECT,key = CACHE_LIST_KEY)   //刪除List集合緩存
public void saveArticle(ArticleVO article) {
  Article articlePO = dozerMapper.map(article, Article.class);
  articleMapper.insert(articlePO);
}

執(zhí)行完成上面的方法闯参,MySQL數(shù)據(jù)庫新增了一條article記錄;成功之后緩存到中的key為“article::list”的緩存也將被刪除。

3.6 更新一個對象

注意更新對象的時候鹿寨,我們在該方法上面加了兩個緩存注解新博。

  • 下文的CachePut注解的作用是在方法執(zhí)行成功之后,將其返回值放入緩存释移。key = "#article.getId()"表示使用參數(shù)article的id屬性作為緩存key叭披。
  • 下文的CacheEvict注解用于將“article::list”的緩存刪除寥殖,因為某一條記錄的數(shù)據(jù)更新玩讳,就表示原來緩存的List集合數(shù)據(jù)與MySQL數(shù)據(jù)庫中的數(shù)據(jù)不一致,所以把它刪除掉嚼贡。緩存數(shù)據(jù)可以沒有熏纯,但是不能和后端被緩存的關(guān)系數(shù)據(jù)庫數(shù)據(jù)不一致。
@CachePut(value = CACHE_OBJECT,key = "#article.getId()")
@CacheEvict(value = CACHE_OBJECT,key = CACHE_LIST_KEY)
public ArticleVO updateArticle(ArticleVO article) {
  Article articlePO = dozerMapper.map(article,Article.class);
  articleMapper.updateById(articlePO);
  return article;  //為了保證一致性粤策,最后返回的更新結(jié)果樟澜,最好從數(shù)據(jù)庫去查
}

執(zhí)行完成該方法,假如ArticleVO參數(shù)對象的id=1

  • MySQL數(shù)據(jù)庫中的id=1的記錄將被更新
  • 緩存數(shù)據(jù)庫中”article::1“的記錄也將被更新(CachePut)
  • 緩存數(shù)據(jù)庫中”article::list“的記錄將被刪除(CacheEvict)

3.7 更新一個對象(另一種方法)

需要特別注意的是:如果在更新方法上使用CachePut注解叮盘,該方法一定要有數(shù)據(jù)更新之后返回值秩贰,因為返回值就是緩存值。比較簡單的做法是直接將不一致的緩存刪掉柔吼,而不是去更新緩存毒费。這樣操作對于程序員的要求更低,不容易出錯愈魏。緩存數(shù)據(jù)可以沒有觅玻,但是不能和后端被緩存的關(guān)系數(shù)據(jù)庫數(shù)據(jù)不一致。

@Override
@Caching(evict = {
        @CacheEvict(value = CACHE_OBJECT,key = CACHE_LIST_KEY),   //刪除List集合緩存
        @CacheEvict(value = CACHE_OBJECT,key = "#article.getId()")  //刪除單條記錄緩存
})
public void updateArticle(ArticleVO article) {
  Article articlePO = dozerMapper.map(article,Article.class);
  articleMapper.updateById(articlePO);
}

方法不需要有返回值培漏。執(zhí)行完成該方法溪厘,假如ArticleVO參數(shù)對象的id=1

  • MySQL數(shù)據(jù)庫中的id=1的記錄將被更新
  • 緩存數(shù)據(jù)庫中”article::1“的記錄將被刪除
  • 緩存數(shù)據(jù)庫中”article::list“的記錄將被刪除

四.緩存注解配置說明

@Cacheable 通常應用到讀取數(shù)據(jù)的查詢方法上:先從緩存中讀取,如果沒有再調(diào)用方法獲取數(shù)據(jù)牌柄,然后把數(shù)據(jù)查詢結(jié)果添加到緩存中畸悬。如果緩存中查找到數(shù)據(jù),被注解的方法將不會執(zhí)行珊佣。

@Cacheable 主要的參數(shù) 參數(shù)說明 示例
value(cacheNames) 緩存的名稱傻昙,在 spring 配置文件中定義,必須指定至少一個彩扔。當value為數(shù)組的時候會針對同一條記錄做多個緩存妆档。 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”})
key 緩存的 key,可以為空虫碉,如果指定要按照 SpEL 表達式編寫贾惦,如果不指定,則缺省按照方法的所有參數(shù)進行組合 例如: @Cacheable(value=”testcache”,key=”#userName”)
condition 緩存的條件,可以為空须板,使用 SpEL 編寫碰镜,返回 true 或者 false,只有為 true 才進行緩存 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”)

@CachePut通常應用于修改方法配置习瑰,能夠根據(jù)方法的請求參數(shù)對其注解的函數(shù)返回值進行緩存绪颖,和 @Cacheable 不同的是,它每次都會觸發(fā)被注解方法的調(diào)用甜奄。

@CachePut 主要的參數(shù) 參數(shù)說明 示例
value(cacheNames) 緩存的名稱柠横,在 spring 配置文件中定義,必須指定至少一個 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key 緩存的 key课兄,可以為空牍氛,如果指定要按照 SpEL 表達式編寫,如果不指定烟阐,則缺省按照方法的所有參數(shù)進行組合 例如: @Cacheable(value=”testcache”,key=”#userName”)
condition 緩存的條件搬俊,可以為空,使用 SpEL 編寫蜒茄,返回 true 或者 false唉擂,只有為 true 才進行緩存 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”)

@CachEvict 通常應用于刪除方法配置,能夠根據(jù)一定的條件對緩存進行刪除檀葛⊥嫠睿可以清除一條或多條緩存。

@CacheEvict 主要的參數(shù) 參數(shù)說明 示例
value(cacheNames) 緩存的名稱驻谆,在 spring 配置文件中定義卵凑,必須指定至少一個 例如: @CachEvict(value=”mycache”) 或者 @CachEvict(value={”cache1”,”cache2”}
key 緩存的 key,可以為空胜臊,如果指定要按照 SpEL 表達式編寫勺卢,如果不指定,則缺省按照方法的所有參數(shù)進行組合 例如: @CachEvict(value=”testcache”,key=”#userName”)
condition 緩存的條件象对,可以為空黑忱,使用 SpEL 編寫,返回 true 或者 false勒魔,只有為 true 才清空緩存 例如: @CachEvict(value=”testcache”, condition=”#userName.length()>2”)
allEntries 是否清空所有緩存內(nèi)容甫煞,缺省為 false,如果指定為 true冠绢,則方法調(diào)用后將立即清空所有緩存 例如: @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation 是否在方法執(zhí)行前就清空抚吠,缺省為 false,如果指定為 true弟胀,則在方法還沒有執(zhí)行的時候就清空緩存楷力,缺省情況下喊式,如果方法執(zhí)行拋出異常,則不會清空緩存 例如:@CachEvict(value=”testcache”萧朝,beforeInvocation=true)

在實際的生產(chǎn)環(huán)境中岔留,沒有一定之規(guī),哪種注解必須用在哪種方法上检柬,@CachEvict 注解通常也用于更新方法上献联。數(shù)據(jù)的緩存策略,要根據(jù)資源的使用方式何址,做出合理的緩存策略規(guī)劃里逆。保證緩存與業(yè)務數(shù)據(jù)庫的數(shù)據(jù)一致性。并做好測試头朱,對于緩存的正確使用运悲,測試才是王道龄减!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末项钮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子希停,更是在濱河造成了極大的恐慌烁巫,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宠能,死亡現(xiàn)場離奇詭異亚隙,居然都是意外死亡,警方通過查閱死者的電腦和手機违崇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門阿弃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人羞延,你說我怎么就攤上這事渣淳。” “怎么了伴箩?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵入愧,是天一觀的道長。 經(jīng)常有香客問我嗤谚,道長棺蛛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任巩步,我火速辦了婚禮旁赊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘椅野。我一直安慰自己终畅,他們只是感情好钞钙,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著声离,像睡著了一般芒炼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上术徊,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天本刽,我揣著相機與錄音,去河邊找鬼赠涮。 笑死子寓,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的笋除。 我是一名探鬼主播斜友,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼垃它!你這毒婦竟也來了鲜屏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤国拇,失蹤者是張志新(化名)和其女友劉穎洛史,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酱吝,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡也殖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了务热。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忆嗜。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖崎岂,靈堂內(nèi)的尸體忽然破棺而出捆毫,到底是詐尸還是另有隱情,我是刑警寧澤该镣,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布冻璃,位于F島的核電站,受9級特大地震影響损合,放射性物質(zhì)發(fā)生泄漏省艳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一嫁审、第九天 我趴在偏房一處隱蔽的房頂上張望跋炕。 院中可真熱鬧,春花似錦律适、人聲如沸辐烂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纠修。三九已至胳嘲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扣草,已是汗流浹背了牛。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辰妙,地道東北人鹰祸。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像密浑,于是被迫代替她去往敵國和親蛙婴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容