Spring Data Redis 快速上手

概述

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

  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 操作。

基础建设

  想象一下,你是一家跨国餐厅的经理,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 驱动,性能杠杠的。

xml 复制代码
<!-- 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:

xml 复制代码
<!-- 配置连接池需要的依赖 -->
<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 配置,配置信息如下:

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

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

yaml 复制代码
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-active 8 16~64 连接池中最大连接数,使用负值表示没有限制
max-idle 8 和 max-active 接近 最大空闲连接数,空闲连接太多是资源浪费
min-idle 0 2~4 最小空闲连接数,保持基本的连接储备
max-wait -1 3000ms 获取连接的最大等待时间,使用负值表示无限等待
timeout 3s 1~3s 命令执行超时时间,超时会抛异常

核心配置类

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

java 复制代码
@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 乱码(二进制字符),不便于查看,如下图所示:

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

java 复制代码
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 存储
JacksonJsonRedisSerializer json 格式存储
OxmSerializer xml 格式存储

实战演练

  在 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 通用命令

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

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

java 复制代码
@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:

java 复制代码
@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 进行操作:

java 复制代码
@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 进行操作:

java 复制代码
@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 进行操作:

java 复制代码
@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"));
}
相关推荐
Thomas.Sir4 小时前
第9课:SpringBoot4.x Web开发基础:WebMvc自动配置、静态资源、请求参数接收
java·spring boot
m0_639906945 小时前
Linux 操作系统入门:目录结构、服务排查常用命令、Shell 脚本基础
linux·运维·后端
holly就是我6 小时前
AI 编程助手为什么跑偏?用开源 ccglass 看清 Prompt、工具调用和 Token 消耗
人工智能·后端
鸿蒙开发6 小时前
共享 Schema 与 SchemaFactory
前端·后端·harmonyos
用户788477316346 小时前
小程序后端三选一:云开发、.NET WebAPI、Node,我按客户现状分了三种答案
后端
aiopencode6 小时前
局域网设备抓包怎么做?用 TraceEagle 抓电视、游戏机和 IoT 流量
后端·ios
智驭未来掌门人6 小时前
CLion 开发 Qt 项目的优势
后端
EnCi Zheng6 小时前
后端怎么把AI接口稳定安全低成本接进业务:超时重试、密钥隔离与成本熔断清单
人工智能·后端·安全·架构·api
用户52958931668506 小时前
三马代驾实战:抢单池的 Redis 锁 + RabbitMQ 异步消费怎么做
redis