Spring Data Redis 快速上手

0 阅读9分钟

概述

  在快速入门 Spring Data Redis 之前,我们先来做个简单的了解。在 Spring 的生态中,我们使用 Spring Data Redis 来实现对 Redis 的数据访问。可能这个时候,有人会有疑惑,市面上已经有 Redis、Redisson、Lettuce 等优秀的 Java Redis 工具库,为什么还要有 Spring Data Redis 呢?学不动了,头都要秃了!不要慌,我们先来看一张图:

image

  Jedis 是 Redis 官方推出的一款面向 Java 的客户端,提供了很多接口调用。并不是只有 Jedis 可以使用,像jdbc-redis、Jredis也都属于 Redis 的 Java 客户端,但他们之间是无法兼容的。如果在一个项目中使用了 Jedis,然后后来决定弃用掉改用 jdbc-redis 就比较麻烦了。Spring Data Redis 是 Spring Data 模块的一部分,专门用来支持在 Spring 应用程序对 Redis 的轻松配置和访问。Spring Data Redis 提供了统一的操作模板 RedisTemplate,封装了 Jedis、Lettuce 的 API 操作,访问 Redis 数据。所以,实际上,Spring Data Redis 内置真正访问的实际是 Jedis、Lettuce 等 API 操作。

image

基础建设

  想象一下,你是一家跨国餐厅的经理,Redis 是你的冷库,Spring Data Redis 给你配了一个全能管家。你只需要告诉管家:“帮我把这筐苹果(Java 对象)存进 01 号柜。” 管家会自动帮你把苹果打包(序列化)、搬运、锁门,甚至还能把不同种类的货物分门别类(封装 API)。

依赖引入

  在 Spring 中,spring-data-redis 项目对访问 Redis 进行了 API 封装,提供了便捷的访问方式。springBoot 项目中的子模块 spring-boot-starter-data-redis 基于 spring-data-redis 项目,做了二次封装,大大简化了 Redis 的相关配置。如果用的是 Spring Boot,则直接引入 starter,如下所示,它默认集成了 Lettuce 驱动,性能杠杠的。

<!-- Spring Data Redis 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

  这个 starter 会自动把 Spring Data Redis 的核心类、默认的 Lettuce 连接工厂都引入。如果需要配连接池,还需要额外引入 Apache Commons Pool2:

<!-- 配置连接池需要的依赖 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

配置数据源

  不同版本的 Spring Boot 配置前缀不一样,Spring Boot 2.x 使用以 spring.data.redis 为前缀属性的 RedisProperties 来配置,而 Spring Boot 3.x 则以 spring.data.redis 为前缀属性的 RedisProperties 来配置 Redis。在 resources/application.yml 文件中添加 Redis 配置,配置信息如下:

spring:
  data:
    redis:
      database: 0 # Redis 数据库索引,默认是0
      host: 127.0.0.1 # Redis 服务器地址
      port: 6379 # Redis 服务器连接端口
      password: # Redis 服务器连接密码(默认为空)

  Lettuce 本身是 Netty 实现的,单个连接可以承载很多并发操作。但实际在高并发场景下,个人建议还是配置连接池,主要目的是控制连接资源的占用上限,避免突发的连接风暴。下面是一个相对合理的配置参考:

spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      password: 
      timeout: 3s
      lettuce:
        pool:
          max-active: 16
          max-idle: 8
          min-idle: 2
          max-wait: 3s
参数默认值生产建议简要说明
max-active816~64连接池中最大连接数,使用负值表示没有限制
max-idle8和 max-active 接近最大空闲连接数,空闲连接太多是资源浪费
min-idle02~4最小空闲连接数,保持基本的连接储备
max-wait-13000ms获取连接的最大等待时间,使用负值表示无限等待
timeout3s1~3s命令执行超时时间,超时会抛异常

核心配置类

  Spring Data Redis 提供了两个常用的模板类:StringRedisTemplate 和 RedisTemplate,如下所示:

@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {

    @Bean
    // 当RedisTemplate对象Bean在IOC容器不存在时则会执行此方法创建对象放到IOC容器
    // 若我们自己创建了RedisTemplate的对象Bean时,此方法则不会创建这个Bean对象
    @ConditionalOnMissingBean(name = {"redisTemplate"})
    // 表示当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //  键和值都是Object类型的,默认序列化方式为:JdkSerializationRedisSerializer
        //  JDK方式的序列化会导致我们的键和值转义
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    // 当StringRedisTemplate的Bean对象不存在时则创建此对象放到Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //  键和值都是String类型的,默认序列化方式为:StringRedisSerializer
        return new StringRedisTemplate(redisConnectionFactory);
    }
}

  我们在大多数情况下会使用 RedisTemplate 及其对应包,它把底层连接细节封装掉了,同时又保留了 Redis 的完整操作能力,该模块为Redis交互提供了高级抽象、各种操作视图和丰富的通用接口,用于针对特定的类型或键做不同的操作。其默认的 Redis 序列化方式会导致缓存的 key、value 乱码(二进制字符),不便于查看,如下图所示:

image

  为了解决这个问题,需要自定义序列化配置。创建一个 RedisTemplate 实例,key 统一用 StringRedisSerializer,value 用 GenericJackson2JsonRedisSerializer,这样既保证了可读性,又兼容了对象反序列化。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 配置我们自己的Redis模板,一般我们的key都为String,值为Object
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 为 Template 设置连接工厂
        redisTemplate.setConnectionFactory(factory);

        // 配置String序列化方式
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // 配置JSON序列化方式
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
        
        // key、hash key 用 String 序列化,保证在 Redis 客户端里可读
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        
		// value 用 JSON 序列化,可读且跨语言
        redisTemplate.setValueSerializer(jsonSerializer);
        redisTemplate.setHashValueSerializer(jsonSerializer);

        // 设置默认序列化方式
        redisTemplate.setDefaultSerializer(stringRedisSerializer);
        
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

✅️ 由于本示例仅仅用于演示,因此将简单将字符串存储到 Redis,序列化对象使用 StringRedisSerializer。

  使用 RedisTemplate 进行对象存储时,需要使存储对象实现 Serializable 接口,这样才能够成功将对象进行序列化。Spring Data Redis 提供了多种 Serializer 策略,这对使用 Redis 的开发者而言,实在是非常便捷。Spring Data Redis 提供了内置的 Serializer,如下表所示:

序列化策略简要说明
JdkSerializationRedisSerializer使用 JDK 的序列化手段,数据以字节流存储
StringRedisSerializer字符串编码,数据以 String 存储
JacksonJsonRedisSerializerjson 格式存储
OxmSerializerxml 格式存储

实战演练

  在 Spring Data Redis 中提供了 RedisTemplate 工具类,里面封装了对 Redis 的操作,并且将不同的数据类型的操作 API 封装到了不同的类型中:

API返回类型简要说明
redisTemplate.opsForValue()ValueOperations操作 String 类型数据
redisTemplate.opsForHash()HashOperations操作 Hash 类型数据
redisTemplate.opsForList()ListOperations操作 List 类型数据
redisTemplate.opsForSet()SetOperations操作 Set 类型数据
redisTemplate.opsForZSet()ZSetOperations操作 SortedSet 类型数据
redisTemplate通用命令

image

操作字符串(String):简单的“存”与“取”

  String 是 Redis 中最简单的基本数据类型,也是使用频率最高的类型,可以存储任何数据,比如计数器、验证码、简单缓存。RedisTemplate 通过的 opsForValue() 获取 ValueOperations 提供字符串操作。代码如下:

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Test
public void valueOperationsTest() {
    String key = "spring-data-redis";
    ValueOperations<String, Object> ops = redisTemplate.opsForValue();
    // 写入数据到 Redis
    ops.set(key, "Hi! Spring Data Redis");
    // 从 Redis 获取数据
    System.out.println(ops.get(key));
}

  通过 StringRedisTemplate 类,我们进行了一次 Redis SET 指令的执行。关于 RedisTemplate 是什么,我们先卖个关子,下面再来介绍。我们先来执行下测试方法,执行完成后,我们在控制台查询,看看是否真的执行成功了。

操作哈希(Hash):对象存储的最优解

  Hash 就像是 Java 里的 Map,适合存储对象、购物车、Session 这类需要单独修改字段的数据,而不需要每次都序列化整个对象。通过 RedisTemplate 的 opsForHash() 可以拿到 HashOperations 进行操作,比如存用户信息,不用把整个对象序列化成一个 value,而可以把每个字段作为 hash field:

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Test
public void hashOperationsTest() {
    String key = "user:100";
    HashOperations<String, String, Object> hashOps = redisTemplate.opsForHash();
    // 存入一个 User 的 ID 为 100,属性名是 name,值是 Tom
    hashOps.put(key, "name", "Tom");
    hashOps.put(key, "age", "20");

    Object name = hashOps.get(key, "name");
    System.out.println(name);


    // 取出所有 Key
    Set<String> keys = hashOps.keys(key);
    keys.stream().filter(Objects::nonNull).forEach(System.out::println);

    // 取出所有 Value
    List<Object> values = hashOps.values(key);
    values.stream().filter(Objects::nonNull).forEach(System.out::println);
}

  Hash 最大的好处就是可以只更新某一个字段,不需要重新写入整个对象。比如用户年龄发生变化,直接 hashOps.put 覆盖 age 字段即可。对高频更新的对象缓存来说,Hash 比 String 存整段 JSON 更节省流量和操作时间。

操作列表(List):消息队列的雏形

  List 类型适合做消息队列、最新消息列表这类场景,其有序、可重复,支持两端操作,尤其是左右压入和弹出操作非常直观。通过 RedisTemplate 的 opsForList() 可以拿到 ListOperations 进行操作:

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Test
public void listOperationsTest() {
    String key = "listKey";
    ListOperations<String, Object> listOps = redisTemplate.opsForList();
    
    // 从左侧插入
    Long pushResult = listOps.leftPush(queueKey, "task-1");

    // 从右侧弹出
    Object task = listOps.rightPop(queueKey);

    // 阻塞式弹出,超时 3 秒
    Object taskWithBlock = listOps.rightPop(queueKey, 3, TimeUnit.SECONDS);
    
    // 批量推入
    listOps.leftPushAll(key, "a", "b", "c");
    
    // 范围查询:0 到 -1 代表查询所有
    List<Object> range = listOps.range(key, 0, -1);
    System.out.println(range); // 输出: [c, b, a] (后进先出)
}

✅ 这里要提个醒:List 做消息队列只是轻量方案,消息不消费会一直堆积,没有消费者组的概念,也不能做消息回放。

操作去重集合(Set)

  Set 类型适合存储不重复元素,支持交集、并集、差集等数学集合操作,比如用户标签、已读记录。通过 RedisTemplate 的 opsForSet() 可以拿到 SetOperations 进行操作:

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Test
public void setOperationsTest() {
    SetOperations<String, Object> setOps = redisTemplate.opsForSet();

    setOps.add("article:1001:tags", "java", "redis", "spring");
    Set<Object> tags = setOps.members("article:1001:tags");
    // 判断是否存在
    Boolean isMember = setOps.isMember("article:1001:tags", "java");
}

操作有序集合(ZSet):排行榜神器

  有序集合(ZSet)在集合(Set)基础上增加了分数(score)概念,适合实现排行榜、签到积分这类需要排序的场景,它的每个元素带一个 score,Redis 会根据 score 自动排序。通过 RedisTemplate 的 opsForZSet() 可以拿到 ZSetOperations 进行操作:

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Test
public void zSetOperationsTest() {
    ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
    zSetOps.add("rank", "player1", 100);
    System.out.println(zSetOps.score("rank", "player1"));

    // 给 player1 增加 10 分
    zSetOps.incrementScore("rank", "player1", 10);
    System.out.println(zSetOps.score("rank", "player1"));
}