<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
快取使用步驟:@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 自動化設定中,這個會被預設注入。生成規則如下:
預設的 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 的優先順序高。總結起來就是:
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45