快取一定要使用註解嗎
專案中總會有些資料是訪問頻繁,但是這些資料有基本上不會修改,比如,商品的分類這些的資料,其基本上不會修改,但在處理這些資料的時候往往還需要處理成樹結構等格式,也就多了許多沒有意義的消耗。下面我們來看下spring給我帶來的快取技術Caching。並在結尾會使用redis替換快取管理器,並實現自定義的快取管理器。
基本的上使用就是兩步:
1、開啟基於註解的快取
在啟動類或者快取的配置類上新增@EnableCaching
2快取的註解使用
@Cacheable @CacheEvict @CachePut @Caching @CacheConfig
原理:
在SpringBoot專案啟動時,自定配置類會載入CacheAutoConfiguration的自動配置類。該類會在容器中自動注入以下幾個配置類,根據不用的條件來決定是哪個配置類起作用。
透過執行程式碼發現其預設的快取的配置類是SimpleCacheConfiguration,其給容器中註冊了一個CacheManager:ConcurrentMapCacheManager,最後將資料儲存在ConcurrentMap中
註解的具體使用
官網註解截圖
@Cacheable:
執行流程
1、方法執行之前,先去查詢Cache(快取元件),按照cacheNames指定的名字獲取;(CacheManager先獲取相應的快取),第一次獲取快取如果沒有Cache元件會自動建立。
2、去Cache中查詢快取的內容,使用一個key,預設就是方法的引數;key是按照某種策略生成的;預設是使用keyGenerator生成的,預設使用SimpleKeyGenerator生成key;SimpleKeyGenerator生成key的預設策略;
如果沒有引數;key=new SimpleKey();
如果有一個引數:key=引數的值
如果有多個引數:key=new SimpleKey(params);
3、沒有查到快取就呼叫目標方法;
4、將目標方法返回的結果,放進快取中
@Cacheable標註的方法執行之前先來檢查快取中有沒有這個資料,預設按照引數的值作為key去查詢快取,如果沒有就執行方法並將結果放入快取;以後再來呼叫就可以直接使用快取中的資料;
核心:
1、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】元件
2、key使用keyGenerator生成的,預設是SimpleKeyGenerator
具體引數:
cacheNames/value
:指定快取元件的名字;將方法的返回結果放在哪個快取中,是陣列的方式,可以指定多個快取;
key
:快取資料使用的key;可以用它來指定。預設是使用方法引數的值 1-方法的返回值
編寫SpEL; #i d;引數id的值 #a0 #p0 #root。args[0] getEmp[2]
keyGenerator
:key的生成器;可以自己指定key的生成器的元件id
key/keyGenerator二選一使用;
cacheManager
:指定快取管理器;或者cacheResolver指定獲取解析器 兩者二選一使用
condition
:指定符合條件的情況下才快取;,condition = “#id>0” condition = “#a0>1”:第一個引數的值>1的時候才進行快取
unless
:否定快取;當unless指定的條件為true,方法的返回值就不會被快取;可以獲取到結果進行判斷unless = “#result == null” unless = “#a0==2”:如果第一個引數的值是2,結果不快取;
sync
:是否使用非同步模式
下圖為el表示式的寫法
@CachePut:
既呼叫方法,又更新快取資料;同步更新快取,修改了資料庫的某個資料,同時更新快取;
執行流程:
1、先呼叫目標方法
2、將目標方法的結果快取起來
需要注意的是此處的key屬性要個查詢的key一直,要不最後修改的是一份快取,查詢的是一套快取,從而造成資料的不一致。
@CacheEvict:快取清除
具體引數:
key:
指定要清除的資料
allEntries
= true:指定清除這個快取中所有的資料
beforeInvocation
= false:快取的清除是否在方法之前執行
預設代表快取清除操作是在方法執行之後執行;如果出現異常快取就不會清除
beforeInvocation = true: 代表清除快取操作是在方法執行之前執行,無論方法是否出現異常,快取都清除
@Caching
定義複雜的快取規則
@CacheConfig抽取快取的公共配置
@CacheConfig(cacheNames=“emp”/*,cacheManager = “employeeCacheManager”*/) //抽取快取的公共配置
cacheNames 指定該類全域性的快取名稱
keyGenerator 指定key的生產策略
cacheManager 指定快取管理器
重點:使用Redis替換預設的快取管理器
新增pom依賴
自定義快取管理器(springboot2版本使用,1版本的自己查詢下就可以了 那個比較簡單)
一定要注意 儲存的資料實體類一定以實現序列號介面
Serializable
@Bean
public
RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 使用快取的預設配置
RedisCacheConfiguration config = RedisCacheConfiguration。defaultCacheConfig();
// 使用 GenericJackson2JsonRedisSerializer 作為序列化器
config = config。serializeValuesWith(
RedisSerializationContext。SerializationPair。fromSerializer(
new
GenericJackson2JsonRedisSerializer()));
RedisCacheManager。RedisCacheManagerBuilder builder =
RedisCacheManager。builder(redisConnectionFactory)。cacheDefaults(config);
return
builder。build();
}
上面為什麼會選用GenericJackson2JsonRedisSerializer 作為序列化器呢,以下說下我的踩坑史。JdkSerializationRedisSerializer是預設序列化方式,是最簡單的也是最安全的,只要實現了Serializer介面,實體型別,集合,Map等都能序列化與反序列化,但缺陷是序列化資料很多,會對redis造成更大壓力,且可讀性和跨平臺基本無法實現Jackson2JsonRedisSerializer用的是json的序列化方式,能解決JdkSerializationRedisSerializer帶來的缺陷,但複雜型別(集合,泛型,實體包裝類)反序列化時會報錯,且Jackson2JsonRedisSerializer需要指明序列化的類Class,這代表一個實體類就有一個RedisCacheManager,程式碼冗餘。
最後使用GenericJackson2JsonRedisSerializer,此類不用需要指明序列化的類,寫一個RedisCacheManager即可,程式碼更精簡,複雜型別(集合,泛型)反序列化時不會報錯,檢視可以發現實現原理是在json資料中放一個@class屬性,指定了類的全路徑包名,方便反序列化,以上為最後選擇GenericJackson2JsonRedisSerializer的原因。