也是在整合redis的時(shí)候偶然間發(fā)現(xiàn)spring-cache的。這也是一個(gè)不錯(cuò)的框架,與spring的事務(wù)使用類(lèi)似,只要添加一些注解方法,就可以動(dòng)態(tài)的去操作緩存了,減少代碼的操作。如果這些注解不滿(mǎn)足項(xiàng)目的需求,我們也可以參考spring-cache的實(shí)現(xiàn)思想,使用AOP代理+緩存操作來(lái)管理緩存的使用。
在這個(gè)例子中我使用的是redis,當(dāng)然,因?yàn)閟pring-cache的存在,我們可以整合多樣的緩存技術(shù),例如Ecache、Mamercache等。
下面來(lái)看springcache的具體操作吧!
附上官方的文檔:
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html
redis中整合spring-cache
我代碼的工程還是接著上個(gè)項(xiàng)目來(lái)使用的, 所以可以結(jié)合上一篇博客來(lái)看
http://blog.csdn.net/u011521890/article/details/78070773
或者直接找github,歡迎star
https://github.com/hpulzl/book_recommend
緩存的配置如下
- 在RedisCacheConfig上添加注解
@EnableCaching //加上這個(gè)注解是的支持緩存注解
- 創(chuàng)建RedisCacheManager
/**
* 設(shè)置RedisCacheManager
* 使用cache注解管理redis緩存
*
* @return
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate());
return redisCacheManager;
}
- 自定義緩存的key
/**
* 自定義生成redis-key
*
* @return
*/
@Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... objects) {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName()).append(".");
sb.append(method.getName()).append(".");
for (Object obj : objects) {
sb.append(obj.toString());
}
System.out.println("keyGenerator=" + sb.toString());
return sb.toString();
}
};
}
在RedisCacheConfig中添加以上的代碼,就可以使用springcache的注解了。下面介紹springcache的注解如何使用
spring cache與redis緩存結(jié)合
對(duì)springCache概念的了解
springCache支持透明的添加緩存到應(yīng)用程序,類(lèi)似事務(wù)處理一般,不需要復(fù)雜的代碼支持。
緩存的主要使用方式包括以下兩方面
- 緩存的聲明,需要根據(jù)項(xiàng)目需求來(lái)妥善的應(yīng)用緩存
- 緩存的配置方式,選擇需要的緩存支持,例如Ecache、redis、memercache等
緩存的注解介紹
@Cacheable 觸發(fā)緩存入口
@CacheEvict 觸發(fā)移除緩存
@CacahePut 更新緩存
@Caching 將多種緩存操作分組
@CacheConfig 類(lèi)級(jí)別的緩存注解,允許共享緩存名稱(chēng)
@CacheConfig
該注解是可以將緩存分類(lèi),它是類(lèi)級(jí)別的注解方式。我們可以這么使用它。
這樣的話(huà),UseCacheRedisService的所有緩存注解例如@Cacheable的value值就都為user。
@CacheConfig(cacheNames = "user")
@Service
public class UseCacheRedisService {}
在redis的緩存中顯示如下
127.0.0.1:6379> keys *
1) "user~keys"
2) "user_1"
127.0.0.1:6379> get user~keys
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> type user~keys
zset
127.0.0.1:6379> zrange user~keys 0 10
1) "user_1"
我們注意到,生成的user~keys,它是一個(gè)zset類(lèi)型的key,如果使用get會(huì)報(bào)WRONGTYPE Operation against a key holding the wrong kind of value。這個(gè)問(wèn)題坑了我很久
@Cacheable
一般用于查詢(xún)操作,根據(jù)key查詢(xún)緩存.
- 如果key不存在,查詢(xún)db,并將結(jié)果更新到緩存中。
- 如果key存在,直接查詢(xún)緩存中的數(shù)據(jù)。
查詢(xún)的例子,當(dāng)?shù)谝徊樵?xún)的時(shí)候,redis中不存在key,會(huì)從db中查詢(xún)數(shù)據(jù),并將返回的結(jié)果插入到redis中。
@Cacheable
public List<User> selectAllUser(){
return userMapper.selectAll();
}
調(diào)用方式。
@Test
public void selectTest(){
System.out.println("===========第一次調(diào)用=======");
List<User> list = useCacheRedisService.selectAllUser();
System.out.println("===========第二次調(diào)用=======");
List<User> list2 = useCacheRedisService.selectAllUser();
for (User u : list2){
System.out.println("u = " + u);
}
}
打印結(jié)果,大家也可以試一試
只輸出一次sql查詢(xún)的語(yǔ)句,說(shuō)明第二次查詢(xún)是從redis中查到的。
===========第一次調(diào)用=======
keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
DEBUG [main] - ==> Preparing: SELECT id,name,sex,age,password,account FROM user
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 1
===========第二次調(diào)用=======
keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
u = User{id=1, name='fsdfds', sex='fdsfg', age=24, password='gfdsg', account='gfds'}
redis中的結(jié)果
我們可以看到redis中已經(jīng)存在
com.lzl.redisService.UseCacheRedisService.selectAllUser.記錄了。
這么長(zhǎng)的一串字符key是根據(jù)自定義key值生成的。
127.0.0.1:6379> keys *
1) "user~keys"
2) "com.lzl.redisService.UseCacheRedisService.selectAllUser."
3) "user_1"
127.0.0.1:6379> get com.lzl.redisService.UseCacheRedisService.selectAllUser.
"[\"java.util.ArrayList\",[[\"com.lzl.bean.User\",{\"id\":1,\"name\":\"fsdfds\",\"sex\":\"fdsfg\",\"age\":24,\"password\":\"gfdsg\",\"account\":\"gfds\"}]]]"
@CachePut
一般用于更新和插入操作,每次都會(huì)請(qǐng)求db
通過(guò)key去redis中進(jìn)行操作。
- 如果key存在,更新內(nèi)容
- 如果key不存在,插入內(nèi)容。
/**
* 單個(gè)user對(duì)象的插入操作,使用user+id
* @param user
* @return
*/
@CachePut(key = "\"user_\" + #user.id")
public User saveUser(User user){
userMapper.insert(user);
return user;
}
redis中的結(jié)果
多了一條記錄user_2
127.0.0.1:6379> keys *
1) "user~keys"
2) "user_2"
3) "com.lzl.redisService.UseCacheRedisService.selectAllUser."
4) "user_1"
127.0.0.1:6379> get user_2
"[\"com.lzl.bean.User\",{\"id\":2,\"name\":\"fsdfds\",\"sex\":\"fdsfg\",\"age\":24,\"password\":\"gfdsg\",\"account\":\"gfds\"}]"
@CacheEvict
根據(jù)key刪除緩存中的數(shù)據(jù)。allEntries=true表示刪除緩存中的所有數(shù)據(jù)。
@CacheEvict(key = "\"user_\" + #id")
public void deleteById(Integer id){
userMapper.deleteByPrimaryKey(id);
}
測(cè)試方法
@Test
public void deleteTest(){
useCacheRedisService.deleteById(1);
}
redis中的結(jié)果
user_1已經(jīng)移除掉。
127.0.0.1:6379> keys *
1) "user~keys"
2) "user_2"
3) "com.lzl.redisService.UseCacheRedisService.selectAllUser."
測(cè)試allEntries=true時(shí)的情形。
@Test
public void deleteAllTest(){
useCacheRedisService.deleteAll();
}
@CacheEvict(allEntries = true)
public void deleteAll(){
userMapper.deleteAll();
}
redis中的結(jié)果
redis中的數(shù)據(jù)已經(jīng)全部清空
127.0.0.1:6379> keys *
(empty list or set)
@Caching
通過(guò)注解的屬性值可以看出來(lái),這個(gè)注解將其他注解方式融合在一起了,我們可以根據(jù)需求來(lái)自定義注解,并將前面三個(gè)注解應(yīng)用在一起
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
使用例子如下
@Caching(
put = {
@CachePut(value = "user", key = "\"user_\" + #user.id"),
@CachePut(value = "user", key = "#user.name"),
@CachePut(value = "user", key = "#user.account")
}
)
public User saveUserByCaching(User user){
userMapper.insert(user);
return user;
}
@Test
public void saveUserByCachingTest(){
User user = new User();
user.setName("dkjd");
user.setAccount("dsjkf");
useCacheRedisService.saveUserByCaching(user);
}
redis中的執(zhí)行結(jié)果
一次添加三個(gè)key
127.0.0.1:6379> keys *
1) "user~keys"
2) "dsjkf"
3) "dkjd"
4) "user_3"
結(jié)合@Caching還可以設(shè)置自定義的注解
自定義緩存注解
@Caching(
put = {
@CachePut(value = "user", key = "\"user_\" + #user.id"),
@CachePut(value = "user", key = "#user.name"),
@CachePut(value = "user", key = "#user.account")
}
)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SaveUserInfo {
}
使用如下
@SaveUserInfo
public User saveUserByInfo(User user){
userMapper.insert(user);
return user;
}
測(cè)試
@Test
public void saveUserByInfoTest(){
User user = new User();
user.setName("haha");
user.setAccount("hhhcc");
useCacheRedisService.saveUserByInfo(user);
}
redis結(jié)果
127.0.0.1:6379> keys *
1) "user_4"
2) "dsjkf"
3) "dkjd"
4) "user~keys"
5) "haha"
6) "hhhcc"
7) "user_3"
通過(guò)以上的例子基本可以了解springcache的使用了,當(dāng)然還有更加復(fù)雜的操作,這里只是簡(jiǎn)單的介紹一下,運(yùn)用到實(shí)際的項(xiàng)目中還是有所欠缺的。不過(guò)有這個(gè)基礎(chǔ)應(yīng)該不會(huì)太難。同時(shí)有時(shí)間可以再研究一下spring-cache的實(shí)現(xiàn)原理。是基于AOP的實(shí)現(xiàn)的,這也是我們?cè)陧?xiàng)目中學(xué)習(xí)的地方。
遇到的兩個(gè)問(wèn)題
WRONGTYPE Operation against a key holding the wrong kind of value
這個(gè)就是上面所說(shuō)的類(lèi)型不一致,使用redis命令不當(dāng)造成的。所以在查找redis的value時(shí)候,需要知道key的類(lèi)型。
type key
Invalid argument(s)
還是redis現(xiàn)實(shí)的錯(cuò)誤,這個(gè)有些困惑,在get的時(shí)候,一定要加上""(引號(hào))才行。
127.0.0.1:6379> keys *
1) "user_4"
2) "com.lzl.redisService.UseCacheRedisService.saveUserByErr.User{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}"
3) "dsjkf"
4) "dkjd"
5) "user~keys"
6) "haha"
7) "hhhcc"
8) "user_3"
127.0.0.1:6379> get com.lzl.redisService.UseCacheRedisService.saveUserByErr.User{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}
Invalid argument(s)
127.0.0.1:6379> type com.lzl.redisService.UseCacheRedisService.saveUserByErr.User{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}
Invalid argument(s)
127.0.0.1:6379> get "com.lzl.redisService.UseCacheRedisService.saveUserByErr.User{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}"
"[\"com.lzl.bean.User\",{\"id\":5,\"name\":\"fsdsg\",\"sex\":\"vcxvx\",\"age\":24,\"password\":\"vcxvcxc\",\"account\":\"vxcvxc\"}]"