Spring Boot 使用Redis缓存
本文示例源码,请看这里
Spring Cache的官方文档,请看这里
缓存存储
Spring 提供了很多缓存管理器,例如:
- SimpleCacheManager
- EhCacheCacheManager
- CaffeineCacheManager
- GuavaCacheManager
- CompositeCacheManager
这里我们要用的是除了核心的Spring框架之外,Spring Data提供的缓存管理器:RedisCacheManager
在Spring Boot中通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),默认情况下Spring Boot根据下面的顺序自动检测缓存提供者:
- Generic
- JCache (JSR-107)
- EhCache 2.x
- Hazelcast
- Infinispan
- Redis
- Guava
- Simple
但是因为我们之前已经配置了redisTemplate了,Spring Boot无法就无法自动给RedisCacheManager设置redisTemplate了,所以接下来要自己配置CacheManager 。
- 首先修改RedisConfig配置类,添加@EnableCaching注解,并继承CachingConfigurerSupport,重写CacheManager 方法
...
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.afterPropertiesSet();
setSerializer(redisTemplate);
return redisTemplate;
}
private void setSerializer(RedisTemplate<String, String> template) {
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(jackson2JsonRedisSerializer);
}
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
// 设置缓存过期时间,秒
rcm.setDefaultExpiration(60);
return rcm;
}
...
Spring提供了如下注解来声明缓存规则:
- @Cacheable triggers cache population
- @CacheEvict triggers cache eviction
- @CachePut updates the cache without interfering with the method execution
- @Caching regroups multiple cache operations to be applied on a method
- @CacheConfig shares some common cache-related settings at class-level
| 注 解 | 描 述 |
|---|---|
| @Cacheable | 表明Spring在调用方法之前,首先应该在缓存中查找方法的返回值。如果这个值能够找到,就会返回缓存的值。否则的话,这个方法就会被调用,返回值会放到缓存之中 |
| @CachePut | 表明Spring应该将方法的返回值放到缓存中。在方法的调用前并不会检查缓存,方法始终都会被调用 |
| @CacheEvict | 表明Spring应该在缓存中清除一个或多个条目 |
| @Caching | 这是一个分组的注解,能够同时应用多个其他的缓存注解 |
| @CacheConfig | 可以在类层级配置一些共用的缓存配置 |
| 属 性 | 类 型 | 描 述 |
|---|---|---|
| value | String[] | 要使用的缓存名称 |
| condition | String | SpEL表达式,如果得到的值是false的话,不会将缓存应用到方法调用上 |
| key | String | SpEL表达式,用来计算自定义的缓存key |
| unless | String | SpEL表达式,如果得到的值是true的话,返回值不会放到缓存之中 |
在一个请求方法上加上@Cacheable注解,测试下效果
@Cacheable(value="testallCache") @RequestMapping(value = "/redis/user/{userId}", method = RequestMethod.GET) public User getUser(@PathVariable() Integer userId) { User user = userService.getUserById(userId); return user; }然后访问这个请求,控制台就报错啦。
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:33) at org.springframework.data.redis.cache.RedisCacheKey.serializeKeyElement(RedisCacheKey.java:74) at org.springframework.data.redis.cache.RedisCacheKey.getKeyBytes(RedisCacheKey.java:49) at org.springframework.data.redis.cache.RedisCache$1.doInRedis(RedisCache.java:176) at org.springframework.data.redis.cache.RedisCache$1.doInRedis(RedisCache.java:172) at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:207)原因如下:
先看一下Redis缓存默认的Key生成策略- If no params are given, return SimpleKey.EMPTY.
- If only one param is given, return that instance.
- If more the one param is given, return a SimpleKey containing all parameters.
从上面的生成策略可以知道,上面的缓存testallCache使用的key是整形的userId参数,但是我们之前在redisTemplate里设置了template.setKeySerializer(new StringRedisSerializer());,所以导致类型转换错误。虽然也可以使用SpEL表达式生成Key(详见这里),但是返回结果还是需要是string类型(比如#root.methodName就是,#root.method就不是),更通用的办法是重写keyGenerator定制Key生成策略。
修改RedisConfig类,重写keyGenerator方法:
@Bean public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(":" + method.getName()); for (Object obj : params) { sb.append(":" + obj.toString()); } return sb.toString(); } }; }- 再次进行刚才的请求(分别以1,2作为userId参数),浏览器结果如下图:


使用redisclient工具查看下:


可以看到Redis里保存了:
- 两条string类型的键值对:key就是上面方法生成的结果,value就是user对象序列化成json的结果
- 一个有序集合:其中key为@Cacheable里的value+~keys,分数为0,成员为之前string键值对的key
这时候把userId为1的用户的username改为ansel(原来是ansel1),再次进行https://localhost:8443/redis/user/1 请求,发现浏览器返回结果仍是ansel1,证明确实是从Redis缓存里返回的结果。


缓存更新与删除
更新与删除Redis缓存需要用到@CachePut和@CacheEvict。这时候我发现如果使用上面那种key的生成策略,以用户为例:它的增删改查方法无法保证生成同一个key(方法名不同,参数不同),所以修改一下keyGenerator,使其按照缓存名称+userId方式生成key:
@Bean public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); String[] value = new String[1]; // sb.append(target.getClass().getName()); // sb.append(":" + method.getName()); Cacheable cacheable = method.getAnnotation(Cacheable.class); if (cacheable != null) { value = cacheable.value(); } CachePut cachePut = method.getAnnotation(CachePut.class); if (cachePut != null) { value = cachePut.value(); } CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class); if (cacheEvict != null) { value = cacheEvict.value(); } sb.append(value[0]); for (Object obj : params) { sb.append(":" + obj.toString()); } return sb.toString(); } }; }接下来编写user的增删改查方法:
@CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.userId") @RequestMapping(value = "/redis/user", method = RequestMethod.POST) public User insertUser(@RequestBody User user) { user.setPassword(SystemUtil.MD5(user.getPassword())); userService.insertSelective(user); return user; } @Cacheable(value = "user") @RequestMapping(value = "/redis/user/{userId}", method = RequestMethod.GET) public User getUser(@PathVariable Integer userId) { User user = userService.getUserById(userId); return user; } //#root.caches[0].name:当前被调用方法所使用的Cache, 即"user" @CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.userId") @RequestMapping(value = "/redis/user", method = RequestMethod.PUT) public User updateUser(@RequestBody User user) { user.setPassword(SystemUtil.MD5(user.getPassword())); userService.updateByPrimaryKeySelective(user); return user; } @CacheEvict(value = "user") @RequestMapping(value = "/redis/user/{userId}", method = RequestMethod.DELETE) public void deleteUser(@PathVariable Integer userId) { userService.deleteByPrimaryKey(userId); }因为新增和修改传递的参数为user对象,keyGenerator无法获取到userId,只好使用SpEL显示标明key了。
然后进行测试:
进行insert操作:

插入后,进行get请求:

查看Redis存储:


进行update操作:

更新后,进行get请求:

查看Redis存储:

进行delete操作:

查看Redis存储:

发现user:3的记录已经没有了,只剩user:1,user:2了
一直很想知道网上很多用之前那种keyGenerator方法的,他们是怎么进行缓存更新和删除的,有知道的可以告知下。
Spring Boot 使用Redis缓存的更多相关文章
- Spring Boot 结合 Redis 缓存
Redis官网: 中:http://www.redis.cn/ 外:https://redis.io/ redis下载和安装 Redis官方并没有提供Redis的Windows版本,这里使用微软提供的 ...
- Spring Boot自定义Redis缓存配置,保存value格式JSON字符串
Spring Boot自定义Redis缓存,保存格式JSON字符串 部分内容转自 https://blog.csdn.net/caojidasabi/article/details/83059642 ...
- SpringBoot入门系列(七)Spring Boot整合Redis缓存
前面介绍了Spring Boot 中的整合Mybatis并实现增删改查,.不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/zhangweizhong/category/ ...
- spring boot集成redis缓存
spring boot项目中使用redis作为缓存. 先创建spring boot的maven工程,在pom.xml中添加依赖 <dependency> <groupId>or ...
- Spring Boot Cache Redis缓存
1.集成MyBatis 1.1.引入maven依赖 1.2.生成Mapper 具体可以看MyBatis Generator官网 http://www.mybatis.org/generator/run ...
- (转)spring boot整合redis
一篇写的更清晰的文章,包括redis序列化:http://makaidong.com/ncjava/330749_5285125.html 1.项目目录结构 2.引入所需jar包 <!-- Sp ...
- Spring Boot + Mybatis + Redis二级缓存开发指南
Spring Boot + Mybatis + Redis二级缓存开发指南 背景 Spring-Boot因其提供了各种开箱即用的插件,使得它成为了当今最为主流的Java Web开发框架之一.Mybat ...
- (35)Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】
[本文章是否对你有用以及是否有好的建议,请留言] 本文章牵涉到的技术点比较多:Spring Data JPA.Redis.Spring MVC,Spirng Cache,所以在看这篇文章的时候,需要对 ...
- 玩转spring boot——结合redis
一.准备工作 下载redis的windows版zip包:https://github.com/MSOpenTech/redis/releases 运行redis-server.exe程序 出现黑色窗口 ...
随机推荐
- 2017年Web前端开发工程师薪资越来越高?
放眼全球,不仅在国内的互联网行业,在国外,前端工程师一样是需求旺盛.供不应求的香饽饽.所以在供不应求的前端招聘市场上,优秀的前端工程师才是有话语权的那一方. 前端开发是做什么的? 前端是互联网时代软件 ...
- C#基础 Dictionary存储自定义对象作为键值
程序每次向容器Dictionary中插入数据时,都会判断Key值是否已经存在,如果不存在,则插入.否则抛出异常.那么Dictionary又是如何判断Key值是否存在的呢? 请看下面的代码: cla ...
- 高防TTCDN
TCDN是深圳市云中漫网络科技公司高防CDN产品的品牌名称,既可以防御,也可以达到加速的效果,价格实惠.TTCDN适用于WEB应用,可以隐藏源站服务器IP,有效的减轻源站服务器压力,加快全国各地区线路 ...
- Elasticsearch索引和文档操作
列出所有索引 现在来看看我们的索引 GET /_cat/indices?v 响应 health status index uuid pri rep docs.count docs.deleted st ...
- 《Python编程从入门到实践》第三章_列表简介
什么是列表呢? 官方说明就是由一些列按特点顺序排列的元素组成.其实可以看出很多个字符串的有序组合吧,里面的内容可以随时的删除,增加,修改. 下面这个就是一个列表,python打印列表的时候会将中括号和 ...
- [python]-数据科学库Numpy学习
一.Numpy简介: Python中用列表(list)保存一组值,可以用来当作数组使用,不过由于列表的元素可以是任何对象,因此列表中所保存的是对象的指针.这样为了保存一个简单的[1,2,3],需要有3 ...
- [BZOJ1415]聪聪和可可
Input 数据的第1行为两个整数N和E,以空格分隔,分别表示森林中的景点数和连接相邻景点的路的条数. 第2行包含两个整数C和M,以空格分隔,分别表示初始时聪聪和可可所在的景点的编号. 接下来E行,每 ...
- 利用浏览器查找font-family的css编码
提供一种利用Chrome快速查找字体编码的小技巧 打开浏览器,按下键盘F12 点击Console控制台 输入escape("要查询的字体中文名称")(注意:括号与引号都是英文输入法 ...
- linux 下用renameTo方法修改java web项目中文件夹名称问题
经测试,在Linux环境中安装tomcat,然后启动其中的项目,在项目中使用java.io.File.renameTo(File dest)方法可行. 之前在本地运行代码可以修改,然后传到Linux服 ...
- Cordova各个插件使用介绍系列(四)—canvas2ImagePlugin保存二维码到手机本地
详情链接地址:http://www.ncloud.hk/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/cordova-4-canvas2imageplugin/ 在前面几篇 ...