spring,springboot 快速完成快取庫的增刪改查@Cacheable、@CachePut、@CacheEvict
1.情景展示
從3.1開始,Spring引入了對Cache的支援。其使用方法和原理都類似於Spring對事務管理的支援。Spring Cache是作用在方法上的,其核心思想是這樣的:
當我們在呼叫一個快取方法時會把該方法引數和返回結果作為一個鍵值對存放在快取中,等到下次利用同樣的引數來呼叫該方法時將不再執行該方法,而是直接從快取中獲取結果進行返回。
spring中內部集成了快取,我們可以拿來直接使用,如何實現對快取的增、刪、改、查操作?
2.@Cacheable、@CachePut、@CacheEvict
通過這三個註解,完全可以實現對快取資料庫的增刪改查。
@Cacheable
使用範圍:
@Cacheable可以標記在一個方法上,也可以標記在一個類上。當標記在一個方法上時表示該方法是支援快取的,當標記在一個類上時則表示該類所有的方法都是支援快取的。
特性:初次將資料存入快取,以後從快取讀取,可以當作讀取操作
對於使用@Cacheable標註的方法,Spring在每次執行前都會檢查Cache中是否存在相同key的快取元素,如果存在就不再執行該方法,而是直接從快取中獲取結果進行返回(不需要再次執行該方法);
否則才會執行並將返回結果存入指定的快取中。
Spring在快取方法的返回值時是以鍵值對進行快取的;
值:就是方法的返回結果;
鍵:支援兩種策略,預設策略和自定義策略。
自定義策略是指我們可以通過Spring的EL表示式來指定我們的key。這裡的EL表示式可以使用方法引數及它們對應的屬性。使用方法引數時我們可以直接使用“#引數名”或者“#p引數index”。
注意:當一個支援快取的方法在物件內部被呼叫時是不會觸發快取功能的。
可用屬性:
- value/cacheNames:兩個等同的引數(cacheNames為Spring 4新增,作為value的別名),用於指定快取儲存的集合名。由於Spring 4中新增了@CacheConfig,因此在Spring 3中原本必須有的value屬性,也成為非必需項了
- key:快取物件儲存在Map集合中的key值,非必需,預設按照函式的所有引數組合作為key值,若自己配置需使用SpEL表示式,比如:@Cacheable(key = "#p0"):使用函式第一個引數作為快取的key值,更多關於SpEL表示式的詳細內容可參考官方文件
- condition:快取物件的條件,非必需,也需使用SpEL表示式,只有滿足表示式條件的內容才會被快取,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示只有當第一個引數的長度小於3的時候才會被快取。
- unless:另外一個快取條件引數,非必需,需使用SpEL表示式。它不同於condition引數的地方在於它的判斷時機,該條件是在函式被呼叫之後才做判斷的,所以它可以通過對result進行判斷。
- keyGenerator:用於指定key生成器,非必需。若需要指定一個自定義的key生成器,我們需要去實現org.springframework.cache.interceptor.KeyGenerator介面,並使用該引數來指定。需要注意的是:該引數與key是互斥的
- cacheManager:用於指定使用哪個快取管理器,非必需。只有當有多個時才需要使用
- cacheResolver:用於指定使用那個快取解析器,非必需。需通過org.springframework.cache.interceptor.CacheResolver介面來實現自己的快取解析器,並用該引數指定。
@Cacheable("userCache") User selectUserById(final Integer id);
@Cacheable(value={"users1", "user2"}, key="caches[1].name") public User find(User user);
@Cacheable(value={"userCache"}, key="#user.id", condition="#user.id%2==0") User getSomeUsers(User user);
@CachePut
使用範圍:@CachePut也可以標註在類上和方法上;
特性:一直往快取中存,可以當作新增或者更新操作
@CachePut也可以宣告一個方法支援快取功能;
與@Cacheable不同的是使用@CachePut標註的方法在執行前不會去檢查快取中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的快取中。
可用屬性:(同上)
@CachePut(value = "userCache") User updateUserById(final Integer id);
@CacheEvict
使用範圍:(同上)
當標記在一個類上時表示其中所有的方法的執行都會觸發快取的清除操作。
特性:一直從快取中刪除,可以當作刪除操作
清除快取
可用屬性:(同上)
@CacheEvict還有下面兩個引數:
- allEntries:非必需,預設為false。當為true時,清除快取中的所有元素
- beforeInvocation:非必需,預設為false,會在呼叫方法之後移除資料。當為true時,會在呼叫方法之前移除資料。
@CacheEvict(cacheNames = {"userCache"}, allEntries = true) public Integer delete(Integer id);
@CacheEvict(cacheNames = {"userCache"}, beforeInvocation = true) public Integer delete(Integer id);
@CacheEvict(value = "userCache") User deleteUserById(final Integer id);
@Caching
使用範圍:(同上)
特性:可同時指定多個Spring Cache相關的註解
可用屬性:
擁有三個屬性:cacheable、put和evict,分別用於指定@Cacheable、@CachePut和@CacheEvict。
@Caching( put = { @CachePut(cacheNames = "userCache", key = "#user.id"), @CachePut(cacheNames = "userCache", key = "#user.username"), @CachePut(cacheNames = "userCache", key = "#user.age") } }
自定義註解
3.解決方案
方式一:提供介面
介面:
/** * 單位資訊快取 * @description: * @author: Marydon * @date: 2020-12-12 9:42 * @version: 1.0 * @email: [email protected] */ public interface IUnitInfoCache { UNITINFO insertOrUpdateCache(String unitKey,UNITINFO unitInfo); UNITINFO getCache(String unitKey); void deleteCache(String unitKey); }
實現類:
import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Component; /** * 單位資訊表快取實現類 * @description: * @author: Marydon * @date: 2020-12-12 10:05 * @version: 1.0 * @email: [email protected] */ @Component @CacheConfig(cacheNames = {"unitInfo2"}) public class UnitInfoCacheImpl implements IUnitInfoCache{ /* * 快取單位資訊 * @description: 存入快取 * @attention: 在執行前不會去檢查快取中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的快取中。 * 指定key後,就不再自動將所有入參當成key的組成部分啦 * @date: 2020年12月12日 0012 9:53 * @param: unitKey 作為快取的主鍵 * @param: unitInfo 作為快取的值 * @return: UNITINFO * 在這裡新增返回值的目的不是為了呼叫返回值,因為我們把它作為入參傳進來了,所以,看似是畫蛇添足; * 實際上是因為@CachePut註解會將返回值作為value存入快取中,所以返回值不能改成void。 */ @CachePut(key = "#unitKey", unless = "#result == null") @Override public UNITINFO insertOrUpdateCache(String unitKey, UNITINFO unitInfo) { return unitInfo; } /* * 獲取單位資訊 * @description: 從快取中取 * @attention: 對於使用@Cacheable標註的方法,Spring在每次執行前都會檢查Cache中是否存在相同key的快取元素,如果存在就不再執行該方法,而是直接從快取中獲取結果進行返回,否則才會執行並將返回結果存入指定的快取中。 * @date: 2020年12月12日 0012 9:55 * @param: unitKey * @return: UNITINFO * 快取中有就取,沒有就執行方法並返回null。 */ @Cacheable(key = "#unitKey", unless = "#result == null") @Override public UNITINFO getCache(String unitKey) { // 快取中沒有對應的可以時,會進來 return null; } /* * 刪除單位資訊 * @description: 從快取中刪除 * @attention: * @date: 2020年12月12日 0012 9:56 * @param: unitKey * @return: void */ @CacheEvict(key = "#unitKey") @Override public void deleteCache(String unitKey) { // 不需要內部實現,@CacheEvict會自動將入參作為key進行移除 } }
方式二:提供父類
父類:
import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; /** * 快取操作父類 * @description: 使用這種方式雖然可以實現多個子類共享方法, * 但是,這些子類需要共用一個cacheName,如果不想共用快取名稱,那就只能重寫這些方法 * @author: Marydon * @date: 2020-12-12 11:04 * @version: 1.0 * @email: [email protected] */ @CacheConfig(cacheNames = {"myCache"}) public abstract class CacheAbstractParent<T> { /* * 存入或更新指定快取 * @description: * @attention: 在執行前不會去檢查快取中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的快取中。 * 指定key後,就不再自動將所有入參當成key的組成部分啦 * @date: 2020年12月12日 0012 9:53 * @param: key 作為快取的主鍵 * @param: javaBean 作為快取的值 * @return: java物件 * 在這裡新增返回值的目的不是為了呼叫返回值,因為我們把它作為入參傳進來了,所以,看似是畫蛇添足; * 實際上是因為@CachePut註解會將返回值作為value存入快取中,所以返回值不能改成void。 */ @CachePut(key = "#key", unless = "#result == null") public T insertOrUpdateCache(String key, T value) { return value; } /* * 獲取單位資訊 * @description: 從快取中取 * @attention: 對於使用@Cacheable標註的方法,Spring在每次執行前都會檢查Cache中是否存在相同key的快取元素,如果存在就不再執行該方法,而是直接從快取中獲取結果進行返回,否則才會執行並將返回結果存入指定的快取中。 * @date: 2020年12月12日 0012 9:55 * @param: key * @return: java物件或null * 快取中有就取,沒有;就執行方法並返回null。 */ @Cacheable(key = "#key", unless = "#result == null") public T getCache(String key) { // 快取中沒有對應的可以時,會進來 return null; } /* * 刪除單位資訊 * @description: 從快取中刪除 * @attention: * @date: 2020年12月12日 0012 9:56 * @param: key * @return: 無返回值 */ @CacheEvict(key = "#key") public void deleteCache(String key) { // 不需要內部實現,@CacheEvict會自動將入參作為key進行移除 } }
子類:
import org.springframework.cache.annotation.CacheConfig; import org.springframework.stereotype.Component; /** * 單位資訊表快取 * @description: 在這裡我們只需要定義好快取的名稱,以及要被快取的物件就可以啦 * 快取的操作:增、刪、改、查,父類已經實現過了。 * 這樣,我們就可以無限擴充套件N個快取實體類 * @author: Marydon * @date: 2020-12-12 11:14 * @version: 1.0 * @email: [email protected] */ @Component // 這裡配置快取名稱無效 // @CacheConfig(cacheNames = {"unitCache"}) public class UnitInfoCache extends CacheAbstractParent<UNITINFO>{ // 繼承了父類所有操作快取的方法 }
4.測試
5.快取策略
如果快取滿了,從快取中移除資料的策略,常見的有FIFO, LRU 、LFU。
- FIFO (First in First Out) 先進先出策略,即先放入快取的資料先被移除
- LRU (Least Recently Used) 最久未使用策略, 即使用時間距離現在最久的那個資料被移除
- LFU (LeastFrequently Used) 最少使用策略,即一定時間內使用次數(頻率)最少的那個資料被移除
- TTL(Time To Live)存活期,即從快取中建立時間點開始至到期的一個時間段(不管在這個時間段內有沒被訪問過都將過期)
- TTI (Time To Idle)空閒期,即一個數據多久沒有被訪問就從快取中移除的時間。
6.快取管理器
通過@EnableCaching註解自動化配置合適的快取管理器(CacheManager),Spring Boot根據下面的順序去偵測快取提供者:
- Generic
- JCache (JSR-107)
- EhCache 2.x
- Hazelcast
- Infinispan
- Redis
- Guava
- Simple
可以通過配置屬性spring.cache.type來強制指定,即:
spring.cache.type=Generic
另外,可通過注入cacheManager來除錯檢視使用哪種型別
@Autowired private CacheManager cacheManager;
寫在最後
哪位大佬如若發現文章存在紕漏之處或需要補充更多內容,歡迎留言!!!