spring cache的使用

spring cache的使用

緩存某些方法的執(zhí)行結(jié)果

設(shè)置好緩存配置之后我們就可以使用 @Cacheable 注解來(lái)緩存方法執(zhí)行的結(jié)果了
spring cache的使用是非常簡(jiǎn)單的,只需要在方法上標(biāo)注 @Cacheable 注解就可以

@Cacheable("getName")  
public String getName(String id) {  
    return xxx;  
}  
  
@Cacheable("getUser")  
public User searchCity(String userId){  
    return this.query(userId);     
}  

整個(gè)@Cacheable的流程是:先從緩存中讀取,如果沒(méi)有再調(diào)用方法獲取數(shù)據(jù),然后把數(shù)據(jù)添加到緩存中.

緩存數(shù)據(jù)一致性保證

在我們配置了查詢用戶名的方法緩存之后,假如這時(shí)候我們改名了,我想讓我自己查詢的時(shí)候能看到我自己的改變。這時(shí)候我們?cè)撛趺醋瞿兀?/p>

@CacheEvict移除緩存

我們這時(shí)候可以在修改方法上加上@CacheEvict注解,來(lái)更新查詢方法的結(jié)果緩存

@CacheEvict(value = "getName", key = "#user.id") //移除指定key的數(shù)據(jù)  
public User updateUser(User user) {  
    users.update(user);  
    return user;  
}  

這時(shí)候再調(diào)用新的查詢接口,我們就不會(huì)走緩存里的方法,會(huì)重新去數(shù)據(jù)庫(kù)中查詢.
因?yàn)檎{(diào)用修改用戶的方法的時(shí)候, @CacheEvict會(huì)根據(jù)相應(yīng)的key和value作為條件從緩存中移除相應(yīng)的數(shù)據(jù).

@CachePut 既要保證方法被調(diào)用,又希望結(jié)果被緩存

據(jù)前面的例子,我們知道,如果使用了 @Cacheable 注釋,則當(dāng)重復(fù)使用相同參數(shù)調(diào)用方法的時(shí)候,方法本身不會(huì)被調(diào)用執(zhí)行,即方法本身被略過(guò)了,取而代之的是方法的結(jié)果直接從緩存中找到并返回了.

現(xiàn)實(shí)中并不總是如此,有些情況下我們希望方法一定會(huì)被調(diào)用,因?yàn)槠涑朔祷匾粋€(gè)結(jié)果,還做了其他事情,例如記錄日志,調(diào)用接口等,這個(gè)時(shí)候,我們可以用 @CachePut 注釋,這個(gè)注釋可以確保方法被執(zhí)行,同時(shí)方法的返回值也被記錄到緩存中.

@Cacheable(value="userCache")
 public User getUserById(String id) { 
   // 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯,直接實(shí)現(xiàn)業(yè)務(wù)
   return getUserFromDB(id); 
 } 

 // 更新 userCache 緩存
 @CachePut(value="userCache",key="#user.id")
 public User updateUser(User user) { 
   return updateUserToDB(user); 
 } 
 
 private User updateUserToDB(User user) { 
   // do some query
   return user; 
 }

如上面的代碼所示,我們首先用 getUserById 方法查詢一個(gè)人的信息,這個(gè)時(shí)候會(huì)查詢數(shù)據(jù)庫(kù)一次,但是也記錄到緩存中了。然后我們修改了用戶信息,調(diào)用了 updateUser 方法,這個(gè)時(shí)候會(huì)執(zhí)行數(shù)據(jù)庫(kù)的更新操作且記錄到緩存,我們?cè)俅涡薷男畔⒉⒄{(diào)用 updateUser 方法,然后通過(guò) getUserById 方法查詢,這個(gè)時(shí)候,由于緩存中已經(jīng)有數(shù)據(jù),所以不會(huì)查詢數(shù)據(jù)庫(kù),而是直接返回最新的數(shù)據(jù)

三種方式的對(duì)比

@Cacheable、@CachePut、@CacheEvict 注釋介紹

  • @Cacheable 主要針對(duì)方法配置,能夠根據(jù)方法的請(qǐng)求參數(shù)對(duì)其結(jié)果進(jìn)行緩存
  • @CachePut 主要針對(duì)方法配置,能夠根據(jù)方法的請(qǐng)求參數(shù)對(duì)其結(jié)果進(jìn)行緩存,和 @Cacheable 不同的是,它每次都會(huì)觸發(fā)真實(shí)方法的調(diào)用
  • @CachEvict 主要針對(duì)方法配置,能夠根據(jù)一定的條件對(duì)緩存進(jìn)行清空

基本原理

一句話介紹就是Spring AOP的動(dòng)態(tài)代理技術(shù)。 如果讀者對(duì)Spring AOP不熟悉的話,可以去看看官方文檔

注意和限制

基于 proxy 的 spring aop 帶來(lái)的內(nèi)部調(diào)用問(wèn)題

上面介紹過(guò) spring cache 的原理,即它是基于動(dòng)態(tài)生成的 proxy 代理機(jī)制來(lái)對(duì)方法的調(diào)用進(jìn)行切面,這里關(guān)鍵點(diǎn)是對(duì)象的引用問(wèn)題.

如果對(duì)象的方法是內(nèi)部調(diào)用(即 this 引用)而不是外部引用,則會(huì)導(dǎo)致 proxy 失效,那么我們的切面就失效,也就是說(shuō)上面定義的各種注釋包括 @Cacheable、@CachePut 和 @CacheEvict 都會(huì)失效,我們來(lái)演示一下。

public Account getAccountByName2(String accountName) { 
   return this.getAccountByName(accountName); 
 } 

 @Cacheable(value="accountCache")// 使用了一個(gè)緩存名叫 accountCache 
 public Account getAccountByName(String accountName) { 
   // 方法內(nèi)部實(shí)現(xiàn)不考慮緩存邏輯,直接實(shí)現(xiàn)業(yè)務(wù)
   return getFromDB(accountName); 
 }
 

上面我們定義了一個(gè)新的方法 getAccountByName2,其自身調(diào)用了 getAccountByName 方法,這個(gè)時(shí)候,發(fā)生的是內(nèi)部調(diào)用(this),所以沒(méi)有走 proxy,導(dǎo)致 spring cache 失效

要避免這個(gè)問(wèn)題,就是要避免對(duì)緩存方法的內(nèi)部調(diào)用,或者避免使用基于 proxy 的 AOP 模式,可以使用基于 aspectJ 的 AOP 模式來(lái)解決這個(gè)問(wèn)題。

@CacheEvict 的可靠性問(wèn)題

我們看到,@CacheEvict 注釋有一個(gè)屬性 beforeInvocation,缺省為 false,即缺省情況下,都是在實(shí)際的方法執(zhí)行完成后,才對(duì)緩存進(jìn)行清空操作。期間如果執(zhí)行方法出現(xiàn)異常,則會(huì)導(dǎo)致緩存清空不被執(zhí)行。我們演示一下

// 清空 accountCache 緩存
 @CacheEvict(value="accountCache",allEntries=true)
 public void reload() { 
   throw new RuntimeException(); 
 }

我們的測(cè)試代碼如下:

accountService.getAccountByName("someone"); 
   accountService.getAccountByName("someone"); 
   try { 
     accountService.reload(); 
   } catch (Exception e) { 
    //...
   } 
   accountService.getAccountByName("someone"); 

注意上面的代碼,我們?cè)?reload 的時(shí)候拋出了運(yùn)行期異常,這會(huì)導(dǎo)致清空緩存失敗。上面的測(cè)試代碼先查詢了兩次,然后 reload,然后再查詢一次,結(jié)果應(yīng)該是只有第一次查詢走了數(shù)據(jù)庫(kù),其他兩次查詢都從緩存,第三次也走緩存因?yàn)?reload 失敗了。

那么我們?nèi)绾伪苊膺@個(gè)問(wèn)題呢?我們可以用 @CacheEvict 注釋提供的 beforeInvocation 屬性,將其設(shè)置為 true,這樣,在方法執(zhí)行前我們的緩存就被清空了。可以確保緩存被清空。

非 public 方法問(wèn)題

和內(nèi)部調(diào)用問(wèn)題類似,非 public 方法如果想實(shí)現(xiàn)基于注釋的緩存,必須采用基于 AspectJ 的 AOP 機(jī)制

Dummy CacheManager 的配置和作用

有的時(shí)候,我們?cè)诖a遷移、調(diào)試或者部署的時(shí)候,恰好沒(méi)有 cache 容器,比如 memcache 還不具備條件,h2db 還沒(méi)有裝好等,如果這個(gè)時(shí)候你想調(diào)試代碼,豈不是要瘋掉?這里有一個(gè)辦法,在不具備緩存條件的時(shí)候,在不改代碼的情況下,禁用緩存。

方法就是修改 spring的配置文件,設(shè)置一個(gè)找不到緩存就不做任何操作的標(biāo)志位

<cache:annotation-driven /> 
 
   <bean id="simpleCacheManager" class="org.springframework.cache.support.SimpleCacheManager"> 
     <property name="caches"> 
       <set> 
         <bean 
           class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
           p:name="default" /> 
       </set> 
     </property> 
   </bean> 

   <bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
     <property name="cacheManagers"> 
       <list> 
         <ref bean="simpleCacheManager" /> 
       </list> 
     </property> 
     <property name="fallbackToNoOpCache" value="true" /> 
   </bean> 

注意以前的 cacheManager 變?yōu)榱?simpleCacheManager,且沒(méi)有配置 accountCache 實(shí)例,后面的 cacheManager 的實(shí)例是一個(gè) CompositeCacheManager,他利用了前面的 simpleCacheManager 進(jìn)行查詢,如果查詢不到,則根據(jù)標(biāo)志位 fallbackToNoOpCache 來(lái)判斷是否不做任何緩存操作。

使用 guava cache

<bean id="cacheManager" class="org.springframework.cache.guava.GuavaCacheManager">
    <property name="cacheSpecification" value="concurrencyLevel=4,expireAfterAccess=100s,expireAfterWrite=100s" />
    <property name="cacheNames">
        <list>
            <value>dictTableCache</value>
        </list>
    </property>
</bean>

代碼地址

https://github.com/rollenholt/spring-cache-example

參考鏈接

csdn的技術(shù)文:Redis 緩存 + Spring 的集成示例
Ibm的技術(shù)文:注釋驅(qū)動(dòng)的 Spring cache 緩存介紹
開(kāi)濤大神的技術(shù)博客:Spring Cache抽象詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • 本文轉(zhuǎn)自被遺忘的博客 Spring Cache 緩存是實(shí)際工作中非常常用的一種提高性能的方法, 我們會(huì)在許多場(chǎng)景下...
    quiterr閱讀 1,211評(píng)論 0 8
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,272評(píng)論 6 342
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,029評(píng)論 25 709
  • 編譯自:server_names 目錄: 通配符主機(jī)名 正則表達(dá)式主機(jī)名 混雜主機(jī)名 對(duì)主機(jī)名的優(yōu)化 兼容性 ng...
    C86guli閱讀 6,071評(píng)論 0 1

友情鏈接更多精彩內(nèi)容