优雅的缓存解决方案--设置过期时间_米奇罗的博客-程序员宅基地

技术标签: Redis  SpringBoot  Spring  

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)
     * 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值
     */

   /* @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return RedisCacheManager.create(redisConnectionFactory);
    }

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        // 创建一个模板类
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        // 将刚才的redis连接工厂设置到模板类中
        template.setConnectionFactory(factory);
        // 设置key的序列化器
        template.setKeySerializer(new StringRedisSerializer());
        // 设置value的序列化器
        //使用Jackson 2,将对象序列化为JSON
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //json转对象类,不设置默认的会将json转成hashmap
        ObjectMapper 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缓存过期时间
     */

    @Bean
    public 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 {
    
    @Resource
    private 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

参考资料

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/chachapaofan/article/details/88930778

智能推荐

_beginthread还是CreateThread _feixiangcq的博客-程序员宅基地

转:http://www.cublog.cn/u2/75321/showart_1154654.html程序员对于Windows程序中应该用_beginthread还是CreateThread来创建线程,一直有所争论。本文将从对CRT源代码出发探讨这个问题。I. 起因今天一个朋友问我程序中究竟应该使用_beginthread还是CreateThread,并且告诉我如果使用不当可能会有内存泄漏。其实我过去对这个问题也是一知半解,为了对朋友

选择32位单片机简化开发工作_32位单片机精简系统_yazhouren的博客-程序员宅基地

原文地址:http://www.ed-china.com/ART_8800047553_400005_500012_TS_53f5a879.HTM传统上,选择32位单片机(MCU)的关键因素在于中央处理单元(即内核CPU)的选择。直到最近,32位MCU已有基于多种内核(包括某些情况下的专有架构)的产品。因此,嵌入式设计人员要么继续使用一种内核,要么需要花费更多时间学习新的硬件知识和移植现有软件_32位单片机精简系统

ue4 HoloLens学习链接_还是你最丑的博客-程序员宅基地

HoloLens2微软官方开发文档(重要):https://docs.microsoft.com/zh-cn/windows/mixed-reality/develop/unreal/unreal-development-overview?tabs=ue426%2Cmrtk%2CasaUE4开发文档:https://docs.unrealengine.com/4.26/zh-CN/SharingAndReleasing/XRDevelopment/AR/HoloLens/QuickStartEmulat

java代码实现文件在hdfs上的上传与下载_hdfs上传文件 java_茶壶妙的博客-程序员宅基地

public class MyReadAndWrite { static FileSystem fs; static { try { fs=FileSystem.get(new URI("hdfs://192.168.56.122:9000"),new Configuration()); } catch (IOException e) { e.printStackTrace(); } catch _hdfs上传文件 java

Linux 自定义快捷键_kiwwi的博客-程序员宅基地

Linux的一个特色就是其命令多而且功能强大, 频繁输入命令不仅麻烦而且工作量较大,虽然可以使用TAB进行>命令补足,或者使用aliase来简化某个命令的输入,但这并不能从根本上解决敲击键盘次数过多的问题。可以把任何命令或命令的组合指定给键盘上的某个键,可以是Alt+[A-Z],Ctrl-[A-Z], Alt+Shift+[A-Z], F1-F12, Ctrl-F[1-12], Alt-F[1-12], Alt+Shift+F[1-12],还有更不可思议的是甚至还可以为功能键加上“参数”,这样你按下那个键的

关于数据库取出的数据回车换行符无法替换问题!_sql server vbcrlf_烟在找抽的博客-程序员宅基地

vbCrLf:表示用于打印和显示功能的回车符和换行符的组合。 关于数据库取出的数据中含有换行与回车符号,在前台截取或作他用,使用 \n\t 或者 char(10) ,char(13) 程序中无法识别vbCrLf则派上用场!实例: DIM Str AS String = Datatable.Rows(i)("数据字段") DIM sa()_sql server vbcrlf

随便推点

python3 高德行政区域API获取公开的省市区县街道乡镇数据,入MySQL库_高德地图api获取某个区下的街道-程序员宅基地

此次爬取,主要通过调取高德行政区划查询API接口,获取最新的数据信息(省、市、区、乡镇、街道经纬度、行政级别、城市编码、行政编码等),并通过pymysql存入mysql数据库表结构:DROP TABLE IF EXISTS `districtsx`;CREATE TABLE `districtsx` ( `districtId` int(11) NOT NULL AUTO_INCREMENT, `districtPid` int(11) NULL DEFAULT NULL COMMEN_高德地图api获取某个区下的街道

服务器系统状态备份已取消,执行系统状态备份时出错 - Windows Server | Microsoft Docs..._照横塘半天残月的博客-程序员宅基地

尝试在 Windows Server 2008 和 Windows Server 2008 R2 中执行系统状态备份时出现错误消息10/21/2020本文内容本文提供了对尝试对系统状态文件保留的卷执行系统状态备份时发生的错误的解决方案。适用于: WindowsServer 2008 R2 Service Pack 1,Windows Server 2012 R2原始 KB 编号: 94453...

Ubuntu vsftp复制文件到远端时错误,Permission denied_afei2530的博客-程序员宅基地

Ubuntu 下复制文件到远端时错误,Permission denied 失败原因如下:(1)vsftp默认配置不允许上传文件解决办法:修改配置文件 vi /etc/vsftpd.conf.将"write_enable=YES"前面的#去掉,然后保存。最后重启vsftp服务器 : service vsftpd restart(2)权限不够 ..._重启ubuntu ftp不能复制

用Winform MVC 处理Halcon机器视觉的一个例子-程序员宅基地

公司的业务是机器人的系统集成,需要在halcon机器视觉软件处理后,用winform与上位机进行串口的通讯。这次winform采用了MVC的架构。原来我一直以为MVC都是用在ASP.NET上的,这次看到它在Winform上也能大展身手。下图是框架结构图:可以看到有Controller, Model和Viewer文件夹。但是Model文件夹是空的,也就是说这次没有使用Model文件。先看Controller文件夹,里面是MainController.cs它是主业务逻辑: // 主业务逻辑

xp系统打印机服务器报错,XP系统打印机显示Spoolsv.exe应用程序错误如何办?_赭哲的博客-程序员宅基地

XP系统打印机显示Spoolsv.exe应用程序错误如何办??最近许多winXP网友反映,在使用打印机的时候,打印机显示spoolsv.exe应用程序错误,这是如何回事呢?其实这一般是打印后台程序无正常启动或者感染病毒形成的。不过,碰到这样的故障也不要慌,下面就由小编来给我们家烧一下winXP系统打印机显示spoolsv.exe应用程序错误的处理办法。打印后台程序winXP系统打印机显示spool..._spolsv.exe

SAP中财务账期和物料账期系统内表_粨毐不侵的博客-程序员宅基地

SAP中财务账期系统内表:财务账期表:T001BSAP中物料账期系统内表:物料账期表:MARV

推荐文章

热门文章

相关标签