Spring Boot中Redis的基本使用和优雅的接口数据缓存

3,121 阅读4分钟

gitee链接

Spring Boot版本:2.3.4.RELEASE

本文的内容是Spring Boot中redis的使用,以及接口数据缓存的一种十分优雅的实现方式

顺带一提,我使用的Redis可视化工具是QuickRedis

使用redis前的准备工作

  • 导入依赖

    <!--redis依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--spring-boot-starter-cache-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    
  • 配置文件连接redis服务器

    spring:
      redis:
        host: myserverhost
        port: 6379
        password: 123456
        jedis:
          pool:
            max-active: 8 # 连接池最大连接数,负值表示无限制
            max-wait: -1 # 连接池最大阻塞等待时间,负值表示无限制
            max-idle: 500 # 连接池中的最大空闲连接
            min-idle: 0 # 连接池中的最小空闲连接
    
  • redis配置类

    package com.cc.config;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.cache.CacheManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    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.StringRedisSerializer;
    
    import java.text.SimpleDateFormat;
    
    @Configuration
    public class RedisConfig {
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(redisConnectionFactory);
    
            // 下面的配置是为了在redis可视化工具中可读
            // ObjectMapper指定在转成json时的一些转换规则
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    
            Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
            // 把自定义objectMapper设置到jackson2JsonRedisSerializer中(可以不设置,使用默认规则)
            jsonRedisSerializer.setObjectMapper(objectMapper);
    
            // RedisTemplate默认的序列化方式使用的是JDK的序列化
            // 设置key的序列化方式
            template.setKeySerializer(new StringRedisSerializer());
            template.setValueSerializer(jsonRedisSerializer);
            template.setHashKeySerializer(new StringRedisSerializer());
            template.setHashValueSerializer(new StringRedisSerializer());
            
            return template;
        }
    }
    

目录

  • RedisTemplate和StringRedisTemplate的区别和使用场景
  • Redis的基本使用
  • 优雅的处理接口数据缓存

RedisTemplate和StringRedisTemplate的区别和使用场景

两者的区别是他们使用的序列化类不同,

  • RedisTemplate使用的是JdkSerializationRedisSerializer

  • StringRedisTemplate使用的是StringRedisSerializer

RedisTemplate的默认序列化类JdkSerializationRedisSerializer在操作数据的时候,会将数据序列化成字节数组,如果缓存对象,需要对象实现Serializable接口。

这种序列化方式缓存的数据在Redis可视化工具上是不可读的。

场景:

当数据是对象类型,并且取出的时候不做任何转换,直接获取一个对象时,这种方式会比较好。

StringRedisTemplate的序列化类StringRedisSerializer只能存储字符串类型的value数据,当存储其他类型时必须转为String

这种序列化方式缓存的数据在Redis可视化工具上是可读的。

场景:

当数据本来就是字符串的时候,就可以用这个。

其他格式

在实际开发过程中,可以根据开发需要选择不同的序列化方式,如:jackson2JsonRedisSerializer、GenericJackson2JsonRedisSerializer等

Redis的基本使用

Redis提供了五大常用类型的操作方法:

  • 字符串String:opsForValue()
  • 列表List:opsForList()
  • 集合Set:opsForSet()
  • 哈希Hash:opsForHash()
  • 有序集合ZSet:opsForZSet()

以下是redisTemplate的示例代码,具体可以在ApplicationTest中看到:

字符串String
@Test
public void stringTest() {
    System.out.println("\n\nredis缓存字符串示例:");
    redisTemplate.opsForValue().set("string", "字符串");
    // 设置过期时间
    redisTemplate.expire("string", 1, TimeUnit.MINUTES);
    String string = redisTemplate.opsForValue().get("string").toString();
    System.out.println("redisTemplate.opsForValue().get(\"string\"): " + string);
    redisTemplate.delete("string");
}
列表List
@Test
public void listTest() {
    System.out.println("\n\nredis缓存列表示例:");

    // 添加元素到数组左侧
    redisTemplate.opsForList().leftPush("list", "a");
    redisTemplate.opsForList().leftPush("list", "b");
    redisTemplate.opsForList().leftPush("list", "c");

    // 添加元素到数组右侧
    redisTemplate.opsForList().rightPush("list", "1");
    redisTemplate.opsForList().rightPush("list", "2");
    redisTemplate.opsForList().rightPush("list", "3");

    // 批量添加元素到数组左侧/右侧
    redisTemplate.opsForList().leftPushAll("list", "q", "w", "e", "r");
    redisTemplate.opsForList().rightPushAll("list", "5", "6", "7", "8");

    // 获取列表指定位置的值
    System.out.println("redisTemplate.opsForList().index(\"list\", 0): " + redisTemplate.opsForList().index("list", 0));
    System.out.println("redisTemplate.opsForList().index(\"list\", 100): " + redisTemplate.opsForList().index("list", 100));

    // 获取指定区间的值
    System.out.println("redisTemplate.opsForList().range(\"list\", 0, 3): " + redisTemplate.opsForList().range("list", 0, 3));

    // 获取列表所有的值
    System.out.println("redisTemplate.opsForList().range(\"list\", 0, -1): " + redisTemplate.opsForList().range("list", 0, -1));
    redisTemplate.expire("list", 10, TimeUnit.SECONDS);

    // 如果存在集合则添加元素
    redisTemplate.delete("list");
    redisTemplate.opsForList().leftPushIfPresent("list", "123");
    redisTemplate.opsForList().rightPushIfPresent("list", "123");
    // 此时应为空
    System.out.println("redisTemplate.opsForList().range(\"list\", 0, -1): " + redisTemplate.opsForList().range("list", 0, -1));

    redisTemplate.opsForList().rightPushAll("list", "a", "b", "c");

    System.out.println("redisTemplate.opsForList().range(\"list\", 0, -1): " + redisTemplate.opsForList().range("list", 0, -1));
    // 在第二个参数所在元素的左边插入第三个参数值
    redisTemplate.opsForList().leftPush("list", "b", "bb");
    System.out.println("redisTemplate.opsForList().range(\"list\", 0, -1): " + redisTemplate.opsForList().range("list", 0, -1));
    // 在第二个参数所在元素的右边插入第三个参数值
    redisTemplate.opsForList().rightPush("list", "a", "aa");
    System.out.println("redisTemplate.opsForList().range(\"list\", 0, -1): " + redisTemplate.opsForList().range("list", 0, -1));

    // 列表元素个数
    System.out.println("redisTemplate.opsForList().size(\"list\"): " + redisTemplate.opsForList().size("list"));

    // 移除元素左边/右边第一个元素
    redisTemplate.opsForList().leftPop("list");
    redisTemplate.opsForList().rightPop("list");

    // 移除右边的元素,同时向左边加入一个元素
    redisTemplate.opsForList().rightPopAndLeftPush("list", "z");

    // 在指定位置插入元素,如果指定位置已有元素会覆盖,没有则新增,超过下标+n会报错
    redisTemplate.opsForList().set("list", 1, "a");
    System.out.println("redisTemplate.opsForList().range(\"list\", 0, -1): " + redisTemplate.opsForList().range("list", 0, -1));

    /**
         * 删除列表中指定的元素,
         * 当l=0时,删除所有相同元素,
         * 当l>0时,从左到右,删除第一个相同元素
         * 当l<0时,从右到左,删除第一个相同元素
         */
    int l = 0;
    redisTemplate.delete("list");
    redisTemplate.opsForList().rightPushAll("list", "a", "b", "c", "a", "b", "c");
    redisTemplate.opsForList().remove("list", -1, "a");
    System.out.println("redisTemplate.opsForList().range(\"list\", 0, -1): " + redisTemplate.opsForList().range("list", 0, -1));

    // 截取元素长度,保留长度内的数据
    redisTemplate.opsForList().trim("list", 0, 3);
    System.out.println("redisTemplate.opsForList().range(\"list\", 0, -1): " + redisTemplate.opsForList().range("list", 0, -1));
}
集合Set
@Test
public void setTest() {
    System.out.println("redis缓存set测试");

    // 向集合内添加值
    resetSet("set");
    redisTemplate.opsForSet().add("set", "a", "b", "c", "c", "d", "d", "f");

    // 获取集合中的值
    Set set = redisTemplate.opsForSet().members("set");
    System.out.println("set: " + set);

    // 集合的长度
    System.out.println("size: " + redisTemplate.opsForSet().size("set"));

    // 随机获取集合中的元素
    System.out.println("redisTemplate.opsForSet().randomMember(\"set\"): " + redisTemplate.opsForSet().randomMember("set"));

    // 随机获取集合中指定数量的元素
    System.out.println("redisTemplate.opsForSet().randomMembers(\"set\", 2): " + redisTemplate.opsForSet().randomMembers("set", 2));

    // 检查指定的元素是否在集合中
    System.out.println("redisTemplate.opsForSet().isMember(\"set\", \"b\"): " + redisTemplate.opsForSet().isMember("set", "b"));

    // 转移变量的元素到目的变量
    boolean move = redisTemplate.opsForSet().move("set", "b", "toSet");
    if (move) {
        System.out.println("redisTemplate.opsForSet().members(\"set\"): " + redisTemplate.opsForSet().members("set"));
        System.out.println("redisTemplate.opsForSet().members(\"toSet\"): " + redisTemplate.opsForSet().members("toSet"));
    }

    // 弹出元素
    redisTemplate.opsForSet().pop("set");

    // 移除多个元素
    Long removeCount = redisTemplate.opsForSet().remove("set", "a", "b");
    System.out.println("removeCount: " + removeCount);
    System.out.println("移除ab后的剩余元素:" + redisTemplate.opsForSet().members("set"));

    {
        // 比较两个set集合,并返回在第一个set中的差值
        resetSampleSet("set");
        resetSampleSetByValus("newSet", ",", "2", "3", "a", "b", "c");

        System.out.println("redisTemplate.opsForSet().members(\"set\"): " + redisTemplate.opsForSet().members("set"));
        System.out.println("redisTemplate.opsForSet().difference(\"set\", \"newSet\"): " + redisTemplate.opsForSet().difference("set", "newSet"));
    }

    {
        // 比较两个set集合,将第一个set中的插值保存到指定set中
        resetSampleSetByValus("set", "a", "b", "c");
        resetSampleSetByValus("newSet", "1", "2", "3");
        redisTemplate.opsForSet().differenceAndStore("set", "newSet", "saveSet");
        System.out.println("redisTemplate.opsForSet().members(\"saveSet\"): " + redisTemplate.opsForSet().members("saveSet"));
    }

    {
        // 随机获取不重复的指定数量的元素
        resetSampleSetByValus("set", "1", "2", "3", "a", "b", "c");
        System.out.println(redisTemplate.opsForSet().distinctRandomMembers("set", 2));
    }

    {
        // 获取两个集合中的交集
        resetSampleSetByValus("set", "a", "b", "c", "d", "e", "f", "g");
        resetSampleSetByValus("newSet", "a", "b", "c", "1", "2", "3");
        System.out.println(redisTemplate.opsForSet().intersect("set", "newSet"));
    }

    {
        // 获取两个集合中的交集,并保存到指定集合上
        resetSampleSetByValus("set", "a", "b", "c", "d", "e", "f", "g");
        resetSampleSetByValus("newSet", "a", "b", "c", "1", "2", "3");
        resetSet("saveSet");
        redisTemplate.opsForSet().intersectAndStore("set", "newSet", "saveSet");
        System.out.println("redisTemplate.opsForSet().members(\"saveSet\"): " + redisTemplate.opsForSet().members("saveSet"));
    }

    {
        // 获取两个集合的合集
        resetSampleSetByValus("set", "a", "b", "c", "d", "e", "f", "g");
        resetSampleSetByValus("newSet", "a", "b", "c", "1", "2", "3");
        System.out.println(redisTemplate.opsForSet().union("set", "newSet"));
    }

    {
        // 获取两个集合的合集,并保存到指定集合上
        resetSampleSetByValus("set", "a", "b", "c", "d", "e", "f", "g");
        resetSampleSetByValus("newSet", "a", "b", "c", "1", "2", "3");
        resetSet("saveSet");
        redisTemplate.opsForSet().unionAndStore("set", "newSet", "saveSet");
        System.out.println("redisTemplate.opsForSet().members(\"saveSet\"): " + redisTemplate.opsForSet().members("saveSet"));
    }
}

// 重置set
private void resetSet(String key) {
    redisTemplate.delete(key);
}

// 重置set,并且初始化值
private void resetSampleSet(String key) {
    resetSampleSetByValus(key, "a", "b", "c", "d", "e", "f", "g");
}

// 重置set,并且初始化指定值
private void resetSampleSetByValus(String key, Object... values) {
    redisTemplate.delete(key);
    redisTemplate.opsForSet().add(key, values);
}
哈希Hash
@Test
public void hashTest() {
    System.out.println("redis缓存hash测试");

    // 添加元素
    redisTemplate.opsForHash().put("hash", "key", "value");

    Map<String, String> map = new HashMap<>();
    map.put("name", "cc");
    map.put("age", "10");
    redisTemplate.opsForHash().putAll("hash", map);

    // 查询元素
    System.out.println("redisTemplate.opsForHash().get(\"hash\", \"key\"): " + redisTemplate.opsForHash().get("hash", "key"));

    // 删除元素
    redisTemplate.opsForHash().put("hash", "key1", "value1");
    redisTemplate.opsForHash().put("hash", "key2", "value2");
    redisTemplate.opsForHash().put("hash", "key3", "value3");
    redisTemplate.opsForHash().delete("hash", "key1", "key2");

    // 查询某key是否存在
    System.out.println("redisTemplate.opsForHash().hasKey(\"hash\", \"key1\"): " + redisTemplate.opsForHash().hasKey("hash", "key1"));

    // 批量查询,根据多个key查询value
    System.out.println(redisTemplate.opsForHash().multiGet("hash", Arrays.asList("key1", "key2", "key3")));

    // hash里的所有key和所有value
    System.out.println("redisTemplate.opsForHash().keys(\"hash\"): " + redisTemplate.opsForHash().keys("hash"));
    System.out.println("redisTemplate.opsForHash().values(\"hash\"): " + redisTemplate.opsForHash().values("hash"));

    // 遍历所有键值对
    Map<Object, Object> entries = redisTemplate.opsForHash().entries("hash");
    for(Map.Entry<Object,Object> entry : entries.entrySet()){
        System.out.println(entry.getKey() + ":" + entry.getValue());
    }

    // 自增,如果value是一个整型,redis提供了自增的函数
    redisTemplate.opsForHash().increment("hash", "index", 1);

    // hash表的大小
    System.out.println("redisTemplate.opsForHash().size(\"hash\"): " + redisTemplate.opsForHash().size("hash"));
}
有序集合ZSet
@Test
public void zsetTest() {
    System.out.println("redis缓存zset测试");

    // 插入元素,并设置分数,元素将会按分数从小到大排序
    redisTemplate.opsForZSet().add("zset", "a", 1);

    // 插入多个元素
    redisTemplate.opsForZSet().add("zset", "q", 1);
    redisTemplate.opsForZSet().add("zset", "w", 2);
    redisTemplate.opsForZSet().add("zset", "e", 3);
    redisTemplate.opsForZSet().add("zset", "r", 4);
    redisTemplate.opsForZSet().add("zset", "q", 4);
    redisTemplate.opsForZSet().add("zset", "q", 5);
    redisTemplate.opsForZSet().add("zset", "q", 6); // 插入相同value,会被覆盖分数

    // 删除元素
    redisTemplate.opsForZSet().remove("zset", "q");

    // 增加元素的分数,并返回增加后的值
    System.out.println("redisTemplate.opsForZSet().incrementScore(\"zset\", \"a\", 10): " + redisTemplate.opsForZSet().incrementScore("zset", "a", 10));

    // 返回元素所在的排名
    System.out.println("redisTemplate.opsForZSet().rank(\"zset\", \"a\"): " + redisTemplate.opsForZSet().rank("zset", "a"));

    // 返回元素所在的排名,按分数从大到小排序
    System.out.println("redisTemplate.opsForZSet().reverseRank(\"hash\", \"a\"): " + redisTemplate.opsForZSet().reverseRank("zset", "a"));

    // 返回指定分数区间内的元素
    System.out.println("redisTemplate.opsForZSet().rangeByScore(\"zset\", 1, 2): " + redisTemplate.opsForZSet().rangeByScore("zset", 1, 5));

    // 返回指定分数区间内的元素,按分数从大到小排序
    System.out.println("redisTemplate.opsForZSet().reverseRangeByScore(\"zset\", 1, 5): " + redisTemplate.opsForZSet().reverseRangeByScore("zset", 1, 5));

    //  返回指定分数区间内,分数在最大值和最小值之间的元素
    System.out.println("redisTemplate.opsForZSet().rangeByScore(\"zset\", 3, 4, 1, 5): " + redisTemplate.opsForZSet().rangeByScore("zset", 3, 4, 1, 5));
    System.out.println("redisTemplate.opsForZSet().reverseRangeByScore(\"zset\", 3, 4, 1, 5): " + redisTemplate.opsForZSet().rangeByScore("zset", 3, 4, 1, 5));

    // 根据分数区间获取集合中元素的数量
    System.out.println("redisTemplate.opsForZSet().count(\"zset\", 1, 5): " + redisTemplate.opsForZSet().count("zset", 1, 5));

    // 获取集合大小
    System.out.println("redisTemplate.opsForZSet().size(\"zset\"): " + redisTemplate.opsForZSet().size("zset"));

    // 获取指定元素的分数
    System.out.println("redisTemplate.opsForZSet().score(\"zset\", \"a\"): " + redisTemplate.opsForZSet().score("zset", "a"));

    // 移除指定索引范围处的元素
    //        redisTemplate.opsForZSet().removeRange("zset", 1, 2);

    // 移除指定分数范围内的成员
    //        redisTemplate.opsForZSet().removeRangeByScore("zset", 1, 5);

    redisTemplate.opsForZSet().add("newZSet", "a", 10);
    redisTemplate.opsForZSet().add("newZSet", "b", 2);
    redisTemplate.opsForZSet().add("newZSet", "c", 3);
    redisTemplate.opsForZSet().add("newZSet", "d", 4);

    // 获取两个集合的合集并保存到指定集合中,同一个元素的分数会相加
    redisTemplate.opsForZSet().unionAndStore("zset", "newZSet", "unionZSet");

    // 获取两个集合的交集并保存到指定集合中,同一个元素的分数会相加
    redisTemplate.opsForZSet().intersectAndStore("zset", "newZSet", "intersectZSet");

    // 遍历集合
    Cursor<ZSetOperations.TypedTuple<Object>> scan = redisTemplate.opsForZSet().scan("zset", ScanOptions.NONE);
    while (scan.hasNext()){
        ZSetOperations.TypedTuple<Object> item = scan.next();
        System.out.println(item.getValue() + ":" + item.getScore());
    }
}

优雅的处理接口数据缓存

当我们熟悉了上面Redis的基本使用之后,要实现接口数据缓存相信不难,实现思路一般是这样:

  1. 响应接口请求

  2. 判断Redis中是否有缓存数据可以获取

    1. 从Redis中取出数据并返回

    2. 从数据库中获取,先存入Redis中再返回

  3. 请求结束

这种方式有一个问题,就是可能会在多个接口都写上第2步的逻辑代码,造成代码冗余,有一种更好的方式去实现,那就是Spring Cache

首先我们需要添加依赖,其中hutool工具不是缓存功能必要的,但是可以辅助我们做到一些事情,后面会说明:

<!--spring-boot-starter-cache-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--Hutool Java工具包-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.5.7</version>
</dependency>

然后在启动类中添加@EnableCaching注解,以开启缓存

package com.cc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class BusinessApplication {
    public static void main(String[] args) {
        SpringApplication.run(BusinessApplication.class, args);
    }
}

重点来了,我们要写关于缓存的配置类:

RedisCacheConfig:

package com.cc.config;

import cn.hutool.json.JSONUtil;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
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.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * Spring Cache Redis,默认key的时候,@RequestBody的对象需要实现toString
 * @author cc
 * @date 2021-07-12 10:19
 */
@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {
    /**
     * 过期时间配置,当需要设置其他缓存时间的时候,在这里添加,并且在本类的 getRedisCacheConfigurationMap 中更新
     * @author cc
     * @date 2021-07-12 10:19
     */
    public interface CacheNames {
        String LIST_DATA = "LIST_DATA"; // 列表数据
    }

    /**
     * 过期时间设置
     * @author cc
     * @date 2021-07-12 10:24
     */
    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        // 时间单位:秒
        redisCacheConfigurationMap.put(CacheNames.LIST_DATA, this.getRedisCacheConfigurationWithTtl(300));
        return redisCacheConfigurationMap;
    }

    /**
     * 缓存key的生成策略
     * 最终生成的key 为 cache类注解指定的cacheNames::类名:方法名#参数值1,参数值2...
     * @author cc
     * @date 2021-07-12 10:21
     */
    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return target.getClass().getName() +
                        "-" +
                        method.getName() +
                        "#" +
                        JSONUtil.toJsonStr(params);
            }
        };
    }

    /**
     * 配置缓存管理器
     * @author cc
     * @date 2021-07-12 10:20
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                // 默认缓存时间(秒)
                this.getRedisCacheConfigurationWithTtl(60),
                // 缓存过期时间设置
                this.getRedisCacheConfigurationMap()
        );
    }

    /**
     * redis序列化
     * @author cc
     * @date 2021-07-12 10:20
     */
    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        //关键点,spring cache 的注解使用的序列化都从这来,没有这个配置的话使用的jdk自己的序列化,实际上不影响使用,只是打印出来不适合人眼识别
        return RedisCacheConfiguration.defaultCacheConfig()
                // 将 key 序列化成字符串
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 将 value 序列化成 json
                // Jackson2JsonRedisSerializer虽然效率和速度更快,但是不能反序列化泛型,所以再想到解决办法之前只能先用GenericJackson2JsonRedisSerializer了
//                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer(Object.class)))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                // 设置缓存过期时间,单位秒
                .entryTtl(Duration.ofSeconds(seconds))
                // 不缓存空值
                .disableCachingNullValues();
    }
}

接下来我们模拟两个接口:

  • 获取分页数据
  • 更新分页数据
package com.cc.controller;

import com.cc.config.RedisCacheConfig;
import com.cc.model.MyPageParam;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
public class TestController {
    @Cacheable(value = RedisCacheConfig.CacheNames.LIST_DATA)
    @PostMapping("/list")
    public List<String> list(@RequestBody MyPageParam pageParam) {
        return fakeData(pageParam);
    }

    @CacheEvict(value = RedisCacheConfig.CacheNames.LIST_DATA, allEntries=true)
    @PostMapping("/update")
    public String update() {
        return "更新成功,缓存数据已清除";
    }

    // 模拟分页数据
    private List<String> fakeData(MyPageParam pageParam) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < pageParam.getPageSize(); i++) {
            list.add(pageParam.getPageNum().toString() + "-" + i);
        }
        return list;
    }

    @CacheEvict(value = "*", allEntries=true)
    @PostMapping("/clear")
    public String clear() {
        return "删除所有缓存数据成功";
    }
}

开始测试,重复调用/list接口获取数据:

{
    "pageNum": 1,
    "pageSize": 10
}

通过Redis可视化工具可以看到数据已经被成功缓存。

然后调用/update更新接口后再看,缓存的数据已经被删除了。

撒花

可以看到,在模拟的列表接口中,我们没有写一行Redis缓存的逻辑代码,仅需要在接口处使用@Cacheable注解开启缓存,自定义一个缓存Key,在配置类中默认缓存60秒,如果有定制的需要,就在配置类中增加。