您好,登錄后才能下訂單哦!
本篇內容主要講解“SpringBoot怎么使用@Cacheable進行緩存與取值”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“SpringBoot怎么使用@Cacheable進行緩存與取值”吧!
緩存使用步驟:@Cacheable這個注解,用它就是為了使用緩存的。所以我們可以先說一下緩存的使用步驟:
開啟基于注解的緩存,使用 @EnableCaching 標識在 SpringBoot 的主啟動類上。
標注緩存注解即可
第一步:開啟基于注解的緩存,使用 @EnableCaching 標注在 springboot 主啟動類上
//開啟基于注解的緩存 @EnableCaching @EnableRyFeignClients @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class ZfjgAuthApplication { public static void main(String[] args) { SpringApplication.run(ZfjgAuthApplication.class, args); } }
第二步:標注緩存注解
@Repository public interface DeviceMapper { @Cacheable(cacheNames = "DeviceDO.deviceId") DeviceDO get(String deviceId); @CacheEvict(cacheNames = "DeviceDO.deviceId", key = "#record.deviceId") int insert(DeviceDO record); }
注:這里使用 @Cacheable 注解就可以將運行結果緩存,以后查詢相同的數據,直接從緩存中取,不需要調用方法。
下面介紹一下 @Cacheable 這個注解常用的幾個屬性:
cacheNames
/value
:用來指定緩存組件的名字
key
:緩存數據時使用的 key,可以用它來指定。默認是使用方法參數的值。(這個 key 你可以使用 spEL 表達式來編寫)
keyGenerator
:key 的生成器。 key 和 keyGenerator 二選一使用
cacheManager
:可以用來指定緩存管理器。從哪個緩存管理器里面獲取緩存。
condition
:可以用來指定符合條件的情況下才緩存
unless
:否定緩存。當 unless 指定的條件為 true ,方法的返回值就不會被緩存。當然你也可以獲取到結果進行判斷。(通過 #result 獲取方法結果)
sync
:是否使用異步模式。
@Cacheable 注解在方法上,表示該方法的返回結果是可以緩存的。也就是說,該方法的返回結果會放在緩存中,以便于以后使用相同的參數調用該方法時,會返回緩存中的值,而不會實際執行該方法。
注意,這里強調了一點:參數相同。這一點應該是很容易理解的,因為緩存不關心方法的執行邏輯,它能確定的是:對于同一個方法,如果參數相同,那么返回結果也是相同的。但是如果參數不同,緩存只能假設結果是不同的,所以對于同一個方法,你的程序運行過程中,使用了多少種參數組合調用過該方法,理論上就會生成多少個緩存的 key(當然,這些組合的參數指的是與生成 key 相關的)。下面來了解一下 @Cacheable 的一些參數:
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Cacheable { @AliasFor("cacheNames") String[] value() default {}; @AliasFor("value") String[] cacheNames() default {}; String key() default ""; String keyGenerator() default ""; String cacheManager() default ""; String cacheResolver() default ""; String condition() default ""; String unless() default ""; boolean sync() default false; }
@Cacheable 提供兩個參數來指定緩存名:value、cacheNames,二者選其一即可。這是 @Cacheable 最簡單的用法示例:
@Override @Cacheable("menu") public Menu findById(String id) { Menu menu = this.getById(id); if (menu != null){ System.out.println("menu.name = " + menu.getName()); } return menu; }
在這個例子中,findById 方法與一個名為 menu 的緩存關聯起來了。調用該方法時,會檢查 menu 緩存,如果緩存中有結果,就不會去執行方法了。
其實,按照官方文檔,@Cacheable 支持同一個方法關聯多個緩存。這種情況下,當執行方法之前,這些關聯的每一個緩存都會被檢查,而且只要至少其中一個緩存命中了,那么這個緩存中的值就會被返回。
示例:
@Override @Cacheable({"menu", "menuById"}) public Menu findById(String id) { Menu menu = this.getById(id); if (menu != null){ System.out.println("menu.name = " + menu.getName()); } return menu; } --------- @GetMapping("/findById/{id}") public Menu findById(@PathVariable("id")String id){ Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a"); Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75"); return menu0; }
為了直觀起見,直接將 id 參數寫到代碼里。現在,我們來測試一下,看一下結果:
一個緩存名對應一個被注解的方法,但是一個方法可能傳入不同的參數,那么結果也就會不同,這應該如何區分呢?這就需要用到 key 。在 spring 中,key 的生成有兩種方式:顯式指定和使用 keyGenerator 自動生成。
3.1 KeyGenerator 自動生成
當我們在聲明 @Cacheable 時不指定 key 參數,則該緩存名下的所有 key 會使用 KeyGenerator 根據參數 自動生成。spring 有一個默認的 SimpleKeyGenerator ,在 spring boot 自動化配置中,這個會被默認注入。生成規則如下:
如果該緩存方法沒有參數,返回 SimpleKey.EMPTY ;
如果該緩存方法有一個參數,返回該參數的實例 ;
如果該緩存方法有多個參數,返回一個包含所有參數的 SimpleKey ;
默認的 key 生成器要求參數具有有效的 hashCode() 和 equals() 方法實現。另外,keyGenerator 也支持自定義, 并通過 keyGenerator 來指定。關于 KeyGenerator 這里不做詳細介紹,有興趣的話可以去看看源碼,其實就是使用 hashCode 進行加乘運算。跟 String 和 ArrayList 的 hash 計算類似。
3.2 顯式指定 key
相較于使用 KeyGenerator 生成,spring 官方更推薦顯式指定 key 的方式,即指定 @Cacheable 的 key 參數。
即便是顯式指定,但是 key 的值還是需要根據參數的不同來生成,那么如何實現動態拼接呢?SpEL(Spring Expression Language,Spring 表達式語言) 能做到這一點。下面是一些使用 SpEL 生成 key 的例子。
@Override @Cacheable(value = {"menuById"}, key = "#id") public Menu findById(String id) { Menu menu = this.getById(id); if (menu != null){ System.out.println("menu.name = " + menu.getName()); } return menu; } @Override @Cacheable(value = {"menuById"}, key = "'id-' + #menu.id") public Menu findById(Menu menu) { return menu; } @Override @Cacheable(value = {"menuById"}, key = "'hash' + #menu.hashCode()") public Menu findByHash(Menu menu) { return menu; }
官方說 key 和 keyGenerator 參數是互斥的,同時指定兩個會導致異常。
CacheManager,緩存管理器是用來管理(檢索)一類緩存的。通常來講,緩存管理器是與緩存組件類型相關聯的。我們知道,spring 緩存抽象的目的是為使用不同緩存組件類型提供統一的訪問接口,以向開發者屏蔽各種緩存組件的差異性。那么 CacheManager 就是承擔了這種屏蔽的功能。spring 為其支持的每一種緩存的組件類型提供了一個默認的 manager,如:RedisCacheManager 管理 redis 相關的緩存的檢索、EhCacheManager 管理 ehCache 相關的緩等。
CacheResolver,緩存解析器是用來管理緩存管理器的,CacheResolver 保持一個 cacheManager 的引用,并通過它來檢索緩存。CacheResolver 與 CacheManager 的關系有點類似于 KeyGenerator 跟 key。spring 默認提供了一個 SimpleCacheResolver,開發者可以自定義并通過 @Bean 來注入自定義的解析器,以實現更靈活的檢索。
大多數情況下,我們的系統只會配置一種緩存,所以我們并不需要顯式指定 cacheManager 或者 cacheResolver。但是 spring 允許我們的系統同時配置多種緩存組件,這種情況下,我們需要指定。指定的方式是使用 @Cacheable 的 cacheManager 或者 cacheResolver 參數。
按照官方文檔,cacheManager 和 cacheResolver 是互斥參數,同時指定兩個可能會導致異常。
是否同步,true/false。在一個多線程的環境中,某些操作可能被相同的參數并發地調用,這樣同一個 value 值可能被多次計算(或多次訪問 db),這樣就達不到緩存的目的。針對這些可能高并發的操作,我們可以使用 sync 參數來告訴底層的緩存提供者將緩存的入口鎖住,這樣就只能有一個線程計算操作的結果值,而其它線程需要等待,這樣就避免了 n-1 次數據庫訪問。
sync = true 可以有效的避免緩存擊穿的問題。
調用前判斷,緩存的條件。有時候,我們可能并不想對一個方法的所有調用情況進行緩存,我們可能想要根據調用方法時候的某些參數值,來確定是否需要將結果進行緩存或者從緩存中取結果。比如當我根據年齡查詢用戶的時候,我只想要緩存年齡大于 35 的查詢結果。那么 condition 能實現這種效果。
condition 接收一個結果為 true 或 false 的表達式,表達式同樣支持 SpEL 。如果表達式結果為 true,則調用方法時會執行正常的緩存邏輯(查緩存-有就返回-沒有就執行方法-方法返回不空就添加緩存);否則,調用方法時就好像該方法沒有聲明緩存一樣(即無論傳入了什么參數或者緩存中有些什么值,都會執行方法,并且結果不放入緩存)。下面舉個例子:
我們看一下數據庫,以這兩條數據為例:
我們首先定義一個帶條件的緩存方法:
@Override @Cacheable(value = {"menuById"}, key = "#id", condition = "#conditionValue > 1") public Menu findById(String id, Integer conditionValue) { Menu menu = this.getById(id); if (menu != null){ System.out.println("menu.name = " + menu.getName()); } return menu; }
然后分兩種情況調用(為了直觀可見,直接將 id 寫在代碼中):
@GetMapping("/findById/{id}") public Menu findById(@PathVariable("id")String id){ Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a", 0); Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75", 2); return menu0; }
可以看到,兩次請求都執行方法(因為原來緩存中都沒有數據),但是只有“微服務測試2”緩存了。這說明,只有滿足 condition 條件的調用,結果才會被緩存。接下來我們再請求一遍,看下結果和打印:
可以看到,“微服務測試2”由于已經有了緩存,所以沒有再執行方法體。而“微服務測試0”又一次執行了。
執行后判斷,不緩存的條件。unless 接收一個結果為 true 或 false 的表達式,表達式支持 SpEL。當結果為 true 時,不緩存。舉個例子:
我們先清除 redis 中的數據。然后看看 mysql 中的數據:
然后編寫一個緩存方法(在該方法中,result代表方法的返回值。關于):
@Override @Cacheable(value = {"menuById"}, key = "#id", unless = "#result.type == 'folder'") public Menu findById(String id) { Menu menu = this.getById(id); if (menu != null){ System.out.println("menu.name = " + menu.getName()); } return menu; }
然后調用該方法:
@GetMapping("/findById/{id}") public Menu findById(@PathVariable("id")String id){ Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a"); Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75"); return menu0; }
可以看到,兩次都執行了方法體(其實,unless 條件就是在方法執行完畢后調用,所以它不會影響方法的執行),但是結果只有 menu.type = ‘page’ 的緩存了,說明 unless 參數生效了。
既然 condition 和 unless 都能決定是否進行緩存,那么同時指定這兩個參數并且結果相沖突的時候,會怎么樣呢?我們來試一試。
首先清除 redis 數據,然后在緩存方法上加上 condition=“true”,如:
@Override @Cacheable(value = {"menuById"}, key = "#id", condition = "true", unless = "#result.type == 'folder'") public Menu findById(String id) { Menu menu = this.getById(id); if (menu != null){ System.out.println("menu.name = " + menu.getName()); } return menu; }
其它代碼不變,我們來看一下緩存結果和打印:
可以看到,雖然兩次調用都執行了,但是,type=‘folder’ 的還是被排除了。說明這種情況下,unless 比 condition 優先級要高。接下來我們把 condition=“false”,再來試試,結果:
可以看到,兩次調用的結果都沒有緩存。說明在這種情況下,condition 比 unless 的優先級高。總結起來就是:
condition 不指定相當于 true,unless 不指定相當于 false
condition = false,一定不會緩存;
condition = true,且 unless = true,不緩存;
condition = true,且 unless = false,緩存;
到此,相信大家對“SpringBoot怎么使用@Cacheable進行緩存與取值”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。