一.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>
配置含義:
- name:緩存名稱他膳,與緩存注解的value(cacheNames)屬性值相同。
- maxElementsInMemory:緩存最大個數(shù)蔓腐。
- eternal:緩存對象是否永久有效矩乐,一但設置了龄句,timeout將不起作用回论。
- timeToIdleSeconds:設置對象在失效前的允許閑置時間(單位:秒)散罕。僅當eternal=false對象不是永久有效時使用,可選屬性傀蓉,默認值是0欧漱,也就是可閑置時間無窮大。
- timeToLiveSeconds:設置對象在失效前允許存活時間(單位:秒)葬燎。最大時間介于創(chuàng)建時間和失效時間之間误甚。僅當eternal=false對象不是永久有效時使用,默認是0.谱净,也就是對象存活時間無窮大窑邦。
- overflowToDisk:當內(nèi)存中對象數(shù)量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中壕探。
- diskSpoolBufferSizeMB:這個參數(shù)設置DiskStore(磁盤緩存)的緩存區(qū)大小冈钦。默認是30MB。每個Cache都應該有自己的一個緩沖區(qū)李请。
- maxElementsOnDisk:硬盤最大緩存?zhèn)€數(shù)瞧筛。
- diskPersistent:是否緩存虛擬機重啟期數(shù)據(jù)。
- diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔导盅,默認是120秒较幌。
- memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據(jù)指定的策略去清理內(nèi)存白翻。默認策略是LRU(最近最少使用)乍炉。你可以設置為FIFO(先進先出)或是LFU(較少使用)。
- clearOnFlush:內(nèi)存數(shù)量最大時是否清除滤馍。
- 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 集合對象的查詢緩存
大家要注意Object
和List<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ù)一致性。并做好測試头朱,對于緩存的正確使用运悲,測試才是王道龄减!