优雅的缓存解决方案--设置过期时间
1. 前言
上篇文章介绍了利用 SpringCache 和 Redis 设置缓存,但是SpringCache 注解并不支持设置缓存时间,确实很令人头疼。这篇文章将叫你用最简单的方式解决 SpringCache 和 Redis 设置缓存并设置缓存时间。 此篇文章基于上篇博客,有啥不懂的地方请查看上篇博客。 上篇文章链接:优雅的缓存解决方案--SpringCache和Redis集成(SpringBoot)
2. 配置
@Cacheable注解不支持配置过期时间,所有需要通过配置CacheManneg来配置默认的过期时间和针对每个类或者是方法进行缓存失效时间配置。
解决 可以采用如下的配置信息来解决的设置失效时间问题配置信息
修改配置类
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.io.Serializable;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;/*** @Author: MaoLin* @Date: 2019/3/26 17:04* @Version 1.0*/@Configuration
@EnableCaching
public class RedisConfig implements Serializable {/*** 申明缓存管理器,会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut)* 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值*//* @Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {return RedisCacheManager.create(redisConnectionFactory);}@Beanpublic RedisTemplate redisTemplate(RedisConnectionFactory factory) {// 创建一个模板类RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();// 将刚才的redis连接工厂设置到模板类中template.setConnectionFactory(factory);// 设置key的序列化器template.setKeySerializer(new StringRedisSerializer());// 设置value的序列化器//使用Jackson 2,将对象序列化为JSONJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//json转对象类,不设置默认的会将json转成hashmapObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);template.setValueSerializer(jackson2JsonRedisSerializer);return template;}*//*** 最新版,设置redis缓存过期时间*/@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), this.getRedisCacheConfigurationWithTtl( 60), this.getRedisCacheConfigurationMap() // 指定 key 策略);}private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();//SsoCache和BasicDataCache进行过期时间配置redisCacheConfigurationMap.put("messagCache", this.getRedisCacheConfigurationWithTtl(30 * 60)); redisCacheConfigurationMap.put("userCache", this.getRedisCacheConfigurationWithTtl(60));//自定义设置缓存时间return redisCacheConfigurationMap;}private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {Jackson2JsonRedisSerializer<Object> 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);RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).entryTtl(Duration.ofSeconds(seconds));return redisCacheConfiguration;}
}
复制代码
测试
- 设置缓存名称及缓存时间(如下为60秒)
redisCacheConfigurationMap.put("userCache",this.getRedisCacheConfigurationWithTtl(60));
复制代码
- 使用 加上注解即可
@Cacheable("userCache")
注:名称为配置类里面设置的名称userCache,可设置多个缓存名称及时间
Controller测试类
import com.ml.demo.dao.UserDao;
import com.ml.demo.entity.User;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.Serializable;/*** @Author: MaoLin* @Date: 2019/3/26 17:03* @Version 1.0*/@RestController
public class testController implements Serializable {@Resourceprivate UserDao userDao;/*** 查询出一条数据并且添加到缓存** @param userId* @return*/@RequestMapping("/getUser")@Cacheable("userCache")public User getUser(@RequestParam(required = true) String userId) {System.out.println("如果没有缓存,就会调用下面方法,如果有缓存,则直接输出,不会输出此段话");return userDao.getUser(Integer.parseInt(userId));}/*** 删除一个缓存** @param userId* @return*/@RequestMapping(value = "/deleteUser")@CacheEvict("userCache")public String deleteUser(@RequestParam(required = true) String userId) {return "删除成功";}/*** 添加一条保存的数据到缓存,缓存的key是当前user的id** @param user* @return*/@RequestMapping("/saveUser")@CachePut(value = "userCache", key = "#result.userId +''")public User saveUser(User user) {return user;}/*** 返回结果userPassword中含有nocache字符串就不缓存** @param userId* @return*/@RequestMapping("/getUser2")@CachePut(value = "userCache", unless = "#result.userPassword.contains('nocache')")public User getUser2(@RequestParam(required = true) String userId) {System.out.println("如果走到这里说明,说明缓存没有生效!");User user = new User(Integer.parseInt(userId), "name_nocache" + userId, "nocache");return user;}@RequestMapping("/getUser3")@Cacheable(value = "userCache", key = "#root.targetClass.getName() + #root.methodName + #userId")public User getUser3(@RequestParam(required = true) String userId) {System.out.println("如果第二次没有走到这里说明缓存被添加了");return userDao.getUser(Integer.parseInt(userId));}}复制代码
测试运行及结果
保存缓存
查看缓存
查看redis
一分钟后缓存过期
再查询缓存
控制台运行结果
3. 报错解决
2019-03-31 14:21:05.163 ERROR 17056 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `com.ml.demo.entity.User` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)at [Source: (byte[])"["com.ml.demo.entity.User",{"userId":11,"userName":"\"张三\"","userPassword":"123"}]"; line: 1, column: 29]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.ml.demo.entity.User` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)at [Source: (byte[])"["com.ml.demo.entity.User",{"userId":11,"userName":"\"张三\"","userPassword":"123"}]"; line: 1, column: 29]] with root cause
复制代码
这个 bug 调了好久才解决,其实问题很简单。
原因:
原因是我在该实体类中添加了一个为了方便实例化该类用的构造函数,导致JVM不会添加默认的无参构造函数,而jackson的反序列化需要无参构造函数,因此报错。
Response实体类同理。
解决:
在实体类中补上一个无参构造器即可。
public User() {}
小结&参考资料
小结
利用 Spring 提供的缓存机制(对象)结合Redis 实现缓存其实是很好的方法,但是没有提供设置缓存时间,这个就很不人性化了,Redis 的使用其实 Spring 还提供了 RedisTemplate 和 StringRedisTemplate 这两个类都支持设置缓存时间,如果要是觉得 SpringCache 的使用不太方便,可以利用 RedisTemplate 类自定义 Redis 工具类来实现缓存。
- Git源码,欢迎clone和fork
参考资料
- spring 2.0以上 整合redis和cache后使用@Cacheable 时间失效
- Spring Boot在反序列化过程中:jackson.databind.exc.InvalidDefinitionException cannot deserialize from Object value
优雅的缓存解决方案--设置过期时间相关推荐
- Sringboot2.x整合Redis缓存,设置过期时间
目录结构: pom.xml <dependencies><!--Spring整合redis包 --><dependency><groupId>org.s ...
- 可以设置过期时间的Java缓存Map
前言 最近项目需求需要一个类似于redis可以设置过期时间的K,V存储方式.项目前期暂时不引进redis,暂时用java内存代替. 解决方案 1. ExpiringMap 功能简介 : 1.可设置Ma ...
- redis缓存失效时间设为多少_java操作Redis缓存设置过期时间的方法
关于Redis的概念和应用本文就不再详解了,说一下怎么在java应用中设置过期时间. 在应用中我们会需要使用redis设置过期时间,比如单点登录中我们需要随机生成一个token作为key,将用户的信息 ...
- @cacheable 设置过期时间_缓存面试三连击——聊聊Redis过期策略?内存淘汰机制?再手写一个LRU 吧!...
大家好,今天我和大家想聊一聊有关redis的过期策略的话题. 听到这里你也许会觉得:"我去,我只是个日常搬砖的,这种偏底层的知识点,我需要care吗?" 话虽如此·,但是兄die, ...
- Redis 缓存回收的7种策略volatile设置过期时间及allkeys所有数据范围内
1.基础说明 当redis设置内存使用限制后,当达到内存限制时,Redis将尝试删除key(控制节点的最大使用内存) redis.conf中配置项maxmemory <bytes>或者控制 ...
- spring缓存注解@Cacheable和@CacheEvict,设置过期时间和批量模糊删除
spring缓存注解@Cacheable和@CacheEvict,设置过期时间和批量模糊删除 配置 CacheManager 类 key前缀配置 RedisCache配置 RedisCache 模糊匹 ...
- springboot缓存@Cacheable的使用,及设置过期时间
1,在启动类Application中增加注解@EnableCaching 2,待缓存的方法上方增加@Cacheable注解 @Cacheable(cacheNames = redisContains. ...
- 如何用java操作Redis缓存设置过期时间
如何用java操作Redis缓存设置过期时间?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获. 在应用中我们会需要使用red ...
- java如何保证redis设置过期时间的原子性_2020年4月Redis面试题和答案整理
点关注,不迷路:持续更新Java相关技术及资讯!!! 关注.转发.评论头条号每天分享java 知识,私信回复"源码" 赠送Spring源码分析.Dubbo.Redis.Netty. ...
最新文章
- 量子算命,在线掷筊:一个IBM量子云计算机的应用实践,代码都有了
- 4大案例分析金融机构的大数据应用
- iOS架构-静态库.framework之依赖第三方库(7)
- OpenCV实现RGB颜色空间和HSI颜色空间的相互转换
- 参数构造错误 微信_快速掌握前端开发中的常见错误
- 第一课-并行编程的几个概念
- 动态规划在求解全源最短路径中的应用(JAVA)--Floyd算法
- linux中zip文件编码错误,如何避免在 Linux 下解压 zip 文件时可能出现的乱码情况...
- 学校运动会广播稿计算机,学校运动会广播稿【五篇】
- RTX游戏本助你玩转一线游戏 畅快过大年
- 从具体案例了解知识图谱构建流程
- java复习快速导航
- 个人邮箱地址格式,如何能够正确的书写?
- 深圳哪个驾校比较好?
- Fitness Notes 健身笔记
- vue-loader was used without the corresponding plugin.
- APS计划排程系统和生产排产系统,需要哪些基础资料(一)
- 自媒体新手经常犯,自媒体平台发布作品没收益?分析了这5个原因
- Verilog实现减法器
- 19年6月仔细阅读A篇:游戏界声优