也是在整合redis的时候偶然间发现spring-cache的。这也是一个不错的框架,与spring的事务使用类似,只要添加一些注解方法,就可以动态的去操作缓存了,减少代码的操作。如果这些注解不满足项目的需求,我们也可以参考spring-cache的实现思想,使用AOP代理+缓存操作来管理缓存的使用。
在这个例子中我使用的是redis,当然,因为spring-cache的存在,我们可以整合多样的缓存技术,例如Ecache、Mamercache等。
下面来看springcache的具体操作吧!
附上官方的文档:
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html
redis中整合spring-cache
我代码的工程还是接着上个项目来使用的, 所以可以结合上一篇博客来看
http://blog.csdn.net/u011521890/article/details/78070773
或者直接找github,欢迎star
https://github.com/hpulzl/book_recommend
缓存的配置如下
- 在RedisCacheConfig上添加注解
@EnableCaching //加上这个注解是的支持缓存注解
- 创建RedisCacheManager
/**
* 设置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缓存结合
对springCache概念的了解
springCache支持透明的添加缓存到应用程序,类似事务处理一般,不需要复杂的代码支持。
缓存的主要使用方式包括以下两方面
- 缓存的声明,需要根据项目需求来妥善的应用缓存
- 缓存的配置方式,选择需要的缓存支持,例如Ecache、redis、memercache等
缓存的注解介绍
@Cacheable 触发缓存入口
@CacheEvict 触发移除缓存
@CacahePut 更新缓存
@Caching 将多种缓存操作分组
@CacheConfig 类级别的缓存注解,允许共享缓存名称
@CacheConfig
该注解是可以将缓存分类,它是类级别的注解方式。我们可以这么使用它。
这样的话,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,它是一个zset类型的key,如果使用get会报WRONGTYPE Operation against a key holding the wrong kind of value。这个问题坑了我很久
@Cacheable
一般用于查询操作,根据key查询缓存.
- 如果key不存在,查询db,并将结果更新到缓存中。
- 如果key存在,直接查询缓存中的数据。
查询的例子,当第一查询的时候,redis中不存在key,会从db中查询数据,并将返回的结果插入到redis中。
@Cacheable
public List<User> selectAllUser(){
return userMapper.selectAll();
}
调用方式。
@Test
public void selectTest(){
System.out.println("===========第一次调用=======");
List<User> list = useCacheRedisService.selectAllUser();
System.out.println("===========第二次调用=======");
List<User> list2 = useCacheRedisService.selectAllUser();
for (User u : list2){
System.out.println("u = " + u);
}
}
打印结果,大家也可以试一试
只输出一次sql查询的语句,说明第二次查询是从redis中查到的。
===========第一次调用=======
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
===========第二次调用=======
keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
u = User{id=1, name='fsdfds', sex='fdsfg', age=24, password='gfdsg', account='gfds'}
redis中的结果
我们可以看到redis中已经存在
com.lzl.redisService.UseCacheRedisService.selectAllUser.记录了。
这么长的一串字符key是根据自定义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
一般用于更新和插入操作,每次都会请求db
通过key去redis中进行操作。
- 如果key存在,更新内容
- 如果key不存在,插入内容。
/**
* 单个user对象的插入操作,使用user+id
* @param user
* @return
*/
@CachePut(key = "\"user_\" + #user.id")
public User saveUser(User user){
userMapper.insert(user);
return user;
}
redis中的结果
多了一条记录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
根据key删除缓存中的数据。allEntries=true表示删除缓存中的所有数据。
@CacheEvict(key = "\"user_\" + #id")
public void deleteById(Integer id){
userMapper.deleteByPrimaryKey(id);
}
测试方法
@Test
public void deleteTest(){
useCacheRedisService.deleteById(1);
}
redis中的结果
user_1已经移除掉。
127.0.0.1:6379> keys *
1) "user~keys"
2) "user_2"
3) "com.lzl.redisService.UseCacheRedisService.selectAllUser."
测试allEntries=true时的情形。
@Test
public void deleteAllTest(){
useCacheRedisService.deleteAll();
}
@CacheEvict(allEntries = true)
public void deleteAll(){
userMapper.deleteAll();
}
redis中的结果
redis中的数据已经全部清空
127.0.0.1:6379> keys *
(empty list or set)
@Caching
通过注解的属性值可以看出来,这个注解将其他注解方式融合在一起了,我们可以根据需求来自定义注解,并将前面三个注解应用在一起
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中的执行结果
一次添加三个key
127.0.0.1:6379> keys *
1) "user~keys"
2) "dsjkf"
3) "dkjd"
4) "user_3"
结合@Caching还可以设置自定义的注解
自定义缓存注解
@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;
}
测试
@Test
public void saveUserByInfoTest(){
User user = new User();
user.setName("haha");
user.setAccount("hhhcc");
useCacheRedisService.saveUserByInfo(user);
}
redis结果
127.0.0.1:6379> keys *
1) "user_4"
2) "dsjkf"
3) "dkjd"
4) "user~keys"
5) "haha"
6) "hhhcc"
7) "user_3"
通过以上的例子基本可以了解springcache的使用了,当然还有更加复杂的操作,这里只是简单的介绍一下,运用到实际的项目中还是有所欠缺的。不过有这个基础应该不会太难。同时有时间可以再研究一下spring-cache的实现原理。是基于AOP的实现的,这也是我们在项目中学习的地方。
遇到的两个问题
WRONGTYPE Operation against a key holding the wrong kind of value
这个就是上面所说的类型不一致,使用redis命令不当造成的。所以在查找redis的value时候,需要知道key的类型。
type key
Invalid argument(s)
还是redis现实的错误,这个有些困惑,在get的时候,一定要加上""(引号)才行。
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\"}]"
网友评论