spring的Cache注解和redis的區(qū)別說明
spring Cache注解和redis區(qū)別
1.不支持TTL
即不能設置過期時間 expires time,SpringCache 認為這是各個Cache實現(xiàn)自己去完成的事情,有方案但是只能設置統(tǒng)一的過期時間,明顯不夠靈活。
2.內(nèi)部調(diào)用
非 public 方法上使用注解,會導致緩存無效。內(nèi)部調(diào)用方法的時候不會調(diào)用cache方法。
由于 SpringCache 是基于 Spring AOP 的動態(tài)代理實現(xiàn),由于代理本身的問題,當同一個類中調(diào)用另一個方法,會導致另一個方法的緩存不能使用,這個在編碼上需要注意,避免在同一個類中這樣調(diào)用。如果非要這樣做,可以通過再次代理調(diào)用,如 ((Category)AopContext.currentProxy()).get(category) 這樣避免緩存無效。
3.key的問題
在清除緩存的時候,無法指定多個緩存塊,同時清除多個緩存的key。
Spring Cache注解+redis整合及遇到的坑
背景:項目經(jīng)理讓我做緩存,我心想不是有redis嗎?他說你把Spring Cache注解結(jié)合進來,我只好硬著頭皮來做。
先介紹Spring Cache注解
從3.1開始,Spring引入了對Cache的支持。其使用方法和原理都類似于Spring對事務管理的支持。Spring Cache是作用在方法上的,其核心思想是這樣的:當我們在調(diào)用一個緩存方法時會把該方法參數(shù)和返回結(jié)果作為一個鍵值對存放在緩存中,等到下次利用同樣的參數(shù)來調(diào)用該方法時將不再執(zhí)行該方法,而是直接從緩存中獲取結(jié)果進行返回。所以在使用Spring Cache的時候我們要保證我們緩存的方法對于相同的方法參數(shù)要有相同的返回結(jié)果。
Spring為我們提供了幾個注解來支持Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable標記的方法在執(zhí)行后Spring Cache將緩存其返回結(jié)果,而使用@CacheEvict標記的方法會在方法執(zhí)行前或者執(zhí)行后移除Spring Cache中的某些元素。下面我們將來詳細介紹一下Spring基于注解對Cache的支持所提供的幾個注解。
根據(jù)項目情況只用到@Cacheable,這里面一共有幾個常用的參數(shù),一個是value,這一定必須指定其表示當前方法的返回值是會被緩存在哪個Cache上的,對應Cache的名稱。其可以是一個Cache也可以是多個Cache,當需要指定多個Cache時其是一個數(shù)組。
還有就是key,key屬性是用來指定Spring緩存方法的返回結(jié)果時對應的key的。該屬性支持SpringEL表達式。當我們沒有指定該屬性時,Spring將使用默認策略生成key。
我們這里先來看看自定義策略,至于默認策略會在后文單獨介紹自定義策略是指我們可以通過Spring的EL表達式來指定我們的key。
這里的EL表達式可以使用方法參數(shù)及它們對應的屬性。使用方法參數(shù)時我們可以直接使用“#參數(shù)名”或者“#p參數(shù)index”。最后一個就是condition,有的時候我們可能并不希望緩存一個方法所有的返回結(jié)果。通過condition屬性可以實現(xiàn)這一功能。condition屬性默認為空,表示將緩存所有的調(diào)用情形。
其值是通過SpringEL表達式來指定的,當為true時表示進行緩存處理;當為false時表示不進行緩存處理,即每次調(diào)用該方法時該方法都會執(zhí)行一次。還有幾個別的注解@CacheEvict,@CachePut,基本跟@Cacheable一樣只是作用不一樣換湯不換藥。
當你看到這的時候我相信你,你已經(jīng)對Spring緩存注解了解的很多了。下面就來配置Spring注解與Redis來整合。(本人不做配置Redis)
配置Spring注解與Redis整合
先導入jar包
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.6.0.RELEASE</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.3.2</version> </dependency>
我們在Spring-config.xml中來配置
xmlns:cache="http://www.springframework.org/schema/cache" http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.2.xsd" <cache:annotation-driven cache-manager="cacheManager" /> //這句話一定要加上,要不注解不管用
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> <bean class="com.zswy.mkedu.utils.RedisCache"> <property name="redisTemplate" ref="redisTemplate" /> <!--name一定要與你注解那個方法中value一樣,否則找不到--> <property name="name" value="courseCache" /> </bean> </set> </property> </bean>
來寫RedisCache這個類,讓其實現(xiàn)Cache接口
public class RedisCache implements Cache{ private RedisTemplate<String, Object> redisTemplate; private String name; public RedisTemplate<String, Object> getRedisTemplate() { return redisTemplate; } public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } public Object getNativeCache() { return this.redisTemplate; } public ValueWrapper get(Object key) { final String keyf = (String) key; Object object = null; object = redisTemplate.execute(new RedisCallback<Object>() { public Object doInRedis(RedisConnection connection) throws DataAccessException { byte[] key = keyf.getBytes(); byte[] t = name.getBytes(); byte[] value = connection.hGet(t, key); if (value == null) { return null; } return toObject(value); } }); ValueWrapper obj=(object != null ? new SimpleValueWrapper(object) : null); return obj; } public <T> T get(Object o, Class<T> aClass) { return null; } public void put(Object key, Object value) { final String keyf = (String) key; final Object valuef = value; final long liveTime = 86400; redisTemplate.execute(new RedisCallback<Long>() { public Long doInRedis(RedisConnection connection) throws DataAccessException { byte[] keyb = keyf.getBytes(); byte[] valueb = toByteArray(valuef); byte[] t = name.getBytes(); connection.hSet(t, keyb, valueb); if (liveTime > 0) { connection.expire(keyb, liveTime); } return 1L; } }); } private byte[] toByteArray(Object obj) { byte[] bytes = null; ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); oos.flush(); bytes = bos.toByteArray(); oos.close(); bos.close(); } catch (IOException ex) { ex.printStackTrace(); } return bytes; } private Object toObject(byte[] bytes) { Object obj = null; try { ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bis); obj = ois.readObject(); ois.close(); bis.close(); } catch (IOException ex) { ex.printStackTrace(); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } return obj; } public ValueWrapper putIfAbsent(Object o, Object o1) { return null; } public void evict(Object key) { final String keyf = (String) key; redisTemplate.execute(new RedisCallback<Long>() { public Long doInRedis(RedisConnection connection) throws DataAccessException { return connection.hDel(name.getBytes(), keyf.getBytes()); } }); } public void clear() { // 清楚緩存,需要根據(jù)Cache的name屬性,在redis中模糊查詢相關key值的集合,并全部刪除 redisTemplate.execute(new RedisCallback<String>() { public String doInRedis(RedisConnection connection) throws DataAccessException { byte[] t = name.getBytes(); Set<byte[]> fields = connection.hKeys(t); for (byte[] bs : fields) { connection.hDel(t, bs); } return "ok"; } }); } }
開始使用注解,我是把其作用在倒dao層中
注意:這有一個坑:一般我們都是三層架構(gòu),service層調(diào)dao層,我一開始把注解加到Service實現(xiàn)層,運行的時候Cache機制不觸發(fā)。我開始大量百度,查書,最后發(fā)現(xiàn)根本不會觸發(fā),因為Spring把實現(xiàn)類裝載成為Bean的時候,會用代理包裝一下,所以從Spring Bean的角度看,只有接口里面的方法是可見的,其它的都隱藏了,自然課看不到實現(xiàn)類里面的非接口方法,最后我把注解放到dao層上
這里的key我使用的方法名,一般會使用參數(shù)的屬性,value就是上文在配置文件里配置的name。這樣所有的配置都配好了
運行結(jié)果
這個就是我們存的key
第二個坑就是,你的實體類一定要序列化 實現(xiàn)Serializable,否則就報空指針異常。這個一定要記住
key的生成策略
鍵的生成策略有兩種,一種是默認策略,一種是自定義策略。
默認策略:
默認的key是通過KeyGenerator生成的,其默認策略如下:
1.如果方法沒有參數(shù),則使用0作為key;
2.如果只有一個參數(shù)的話則使用該參數(shù)作為key;
3.如果參數(shù)多于一個則使用所有參數(shù)的hashcode作為key;
自定義策略:
自定義策略是指我們通過Spring的EL表達式來指定我們的key。這里的EL表達式可以使用參數(shù)以及它們對應的屬性。使用方法參數(shù)時我們可以直接使用“#參數(shù)名”或者“#p參數(shù)index”。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Spring注解@Profile實現(xiàn)開發(fā)環(huán)境/測試環(huán)境/生產(chǎn)環(huán)境的切換
在進行軟件開發(fā)過程中,一般會將項目分為開發(fā)環(huán)境,測試環(huán)境,生產(chǎn)環(huán)境。本文主要介紹了Spring如何通過注解@Profile實現(xiàn)開發(fā)環(huán)境、測試環(huán)境、生產(chǎn)環(huán)境的切換,需要的可以參考一下2023-04-04Spring Boot 與 Kotlin 使用JdbcTemplate連接MySQL數(shù)據(jù)庫的方法
本文介紹在Spring Boot基礎下配置數(shù)據(jù)源和通過 JdbcTemplate 編寫數(shù)據(jù)訪問的示例。感興趣的朋友跟隨腳本之家小編一起學習吧2018-01-01Java創(chuàng)建多線程異步執(zhí)行實現(xiàn)代碼解析
這篇文章主要介紹了Java創(chuàng)建多線程異步執(zhí)行實現(xiàn)代碼解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-07-07Java多線程編程之訪問共享對象和數(shù)據(jù)的方法
這篇文章主要介紹了Java多線程編程之訪問共享對象和數(shù)據(jù)的方法,多個線程訪問共享對象和數(shù)據(jù)的方式有兩種情況,本文分別給出代碼實例,需要的朋友可以參考下2015-05-05springboot自帶的緩存@EnableCaching用法
這篇文章主要介紹了springboot自帶的緩存@EnableCaching用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08