Redis--SpringDataRedis详解

发布于:2025-05-23 ⋅ 阅读:(17) ⋅ 点赞:(0)

SpringDataRedis

springData是spring中数据操作的模块,包括对各种数据库的集成,其中对Redis的集成模块就叫SpringDataRedis

官网地址: Spring Data Redis

  • 提供了对不同Redis客户端的整合(Lettuce和Jedis)

  • 提供了RedisTemplate统一API来操作Redis

  • 支持Redis的发布订阅模型

  • 支持Redis哨兵和Redis集群

  • 支持基于Lettuce 的响应式编程

  • 支持基于JDK、JSON、字符串、spring对象的数据序列化及反序列化

  • 支持基于Redis的JDKCollection实现

springdata提供了对Lettuce和Jedis客户端的整合,要整合就需要提供一套统一的标准,所以spring提供了RedisTemplate类,来去封装对于Redis的各种操作的API,底层的实现是由Lettuce和Jedis来实现的,除此以外,spring还在封装的基础上做了很多支持,比如发布订阅的支持,支持Redis哨兵和Redis集群,以及基于Lettuce 的响应式编程的支持,以及底层还支持基于JDK、JSON、字符串、spring对象的数据序列化及反序列化(因为redis底层的String类型底层是字节数组,如果要存入Java对象就需要通过序列化将其转换成字符串,或者字节数组,以及反序列化,将Redis读到的数据转化成Java对象或字符串),最后还支持基于Redis的JDKCollection实现(即JDK中各种各样的集合,又基于Redis重新实现了这些集合,比如链表等,基于Redis的这种实现是分布式的,跨系统的。因此要重新实现)。

如何使用SpringDataRedis来进行redis的操作?

核心为RedisTemplate工具类

快速入门

我们在学习Jedis时,Redis中的所有命令就是Jedis中的方法,这样节省了学习成本,但是显得比较臃肿。

springDataRedis中提供了RedisTemplates工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装了不同的类型中,如下表所示:

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

Redis官方对命令做了分组,比如有通用命令,也有专门操作String类型,Hash类型...等等的命令。而SpringDataRedis的RedisTemplate也做了这种事情,在其内部提供了一系列API,比如opsForValue()(操作String类型),opsForHash()(操作Has类型)...等等,这些API返回的对象类型都是XXXOPerations,比如使用redisTemplate.opsForValue(),就能拿到ValuesOperations对象,这个对象中封装的就是对于字符串的各种操作,以下同上,也就是说RedisTemplates利用对象封装的形式把不同数据类型的方法封装到不同对象里,将来调用时,各司其职,井井有条,不会显得臃肿。而redisTemplate这个类本身封装的是一些通用或者比较特殊的命令。

代码展示:

springboot已经提供了对springDataRedis的支持,使用时非常简单

  • 引入依赖

        
 <!--redis起步依赖-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
         </dependency>
 <!--        连接池依赖-->
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-pool2</artifactId>
         </dependency>

引入redis起步依赖以及连接池依赖(因为不管是Jedis还是Lettuce,底层都会基于common-pool(连接池)来实现连接池效果)

  • 配置Redis的基本信息

spring:
   data:
     redis:
       host: localhost
       port: 6379
       lettuce:
         pool:
           max-active: 8 #  最大连接数
           max-idle: 8 # 最大空闲连接数
           min-idle: 0 # 最小空闲连接数
           max-wait: 100 # 连接等待时间毫秒

注意事项:

因为在spring项目中默认使用Lettuce。

image-20250521173739680

如果想要使用Jedis,需要在pom文件中导入Jedis依赖。

必须要手动配置连接池属性,才能生效

  • 注入RedisTemplate并编写测试

 @SpringBootTest
 class SpringbootRedisApplicationTests {
     @Autowired
     private RedisTemplate  redisTemplate;
     @Test
     void testString(){
         //插入String类型数据
         redisTemplate.opsForValue().set("name","星宇");
         //获取String类型数据
        String name = (String) redisTemplate.opsForValue().get("name");
        System.out.println("name = "+name);
     }
 ​
 }

测试结果:

image-20250521173844600

注意事项:

在RedisTemplate底层有自动的序列化机制。在传入参数时只要是Object类型的即可,在底层可以转成Redis可以处理的字节。

在通过redis控制台查询发现name并不是我们在springboot项目中存入的数据,通过查询keys * ,我们查询到了我们的键中有一个“\xac\xed\x00\x05t\x00\x04name”。我们通过查询这个key得到了 "\xac\xed\x00\x05t\x00\x06\xe6\x98\x9f\xe5\xae\x87"

而这就是我们在springboot项目中存入的value值,为什么呢?

这就提到了我们开始说的redisTemplate的序列化机制,因此,我们通过redisTemplate传入的name以及value值被当做了Java对象,而RedisTemplate底层默认对Java对象的处理方式就是利用JDK的序列化工具,IO流中的序列化流 ObjectOutputStream。

在RedisTemplate中我们可以在其中看到对应的序列化器

image-20250521183525869

image-20250521183748519

由此引出 :

SpringDataRedis的序列化方式

RedisTemplate可以接受任意Object作为值写入Redis,只不过写入前会将Object序列化成字节形式,默认是采用JDK的序列化(ObjectOutputStream)得到结果如下:

image-20250521184200585

缺点:

  • 可读性差 (原本以为修改了name,没想到是新建了一个key,会导致之后的逻辑错误)。

  • 内存占用较大。

如何修改,做到所见即所得呢?

只能去修改redisTemplate的序列化方式。

我们可以找到RedisSerializer接口去看他的实现类。

我们可以看到默认使用的序列化器JDKSerializationRedisSerializer类,不是很好用。

然后还有专门处理字符串的StringRedisSerializer类,字符串转字节写入Redis只需要getbytes()方法即可,不需要去使用JDK序列化器 ,而StringRedisSerializer类底层就是使用getBytes()方法。

使用场景:在key与HashKey都为字符串是可以使用。一般情况下key都为字符串,只有value可能是对象。

如果value是对象,则建议使用Jackson2JsonRedisSerializer类,也就是转json字符串的序列化工具。

总结来说:我们的key一般使用StringRedisSerializer,我们的value一般使用Jackson2JsonRedisSerializer类型。

image-20250521191330165

要修改什么样的序列化方式清楚了,那应该怎样转换呢?

方案一:

我们可以自定义RedisTemplate的序列化方式,代码如下:

 
package com.lyc.springbootredis.redis.config;
 ​
 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.GenericJackson2JsonRedisSerializer;
 import org.springframework.data.redis.serializer.RedisSerializer;
 ​
 @Configuration
 public class RedisConfig {
     @Bean
     public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
         // 创建RedisTemplate对象
         RedisTemplate<String, Object> template = new RedisTemplate<>();
         //  设置连接工厂
         template.setConnectionFactory(redisConnectionFactory);
         // 创建JSON序列化工具
         GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
         // 设置key的序列化
         template.setKeySerializer(RedisSerializer.string());
         template.setHashKeySerializer(RedisSerializer.string());
         // 设置value的序列化
         template.setValueSerializer(jsonRedisSerializer);
         template.setHashValueSerializer(jsonRedisSerializer);
         //返回
         return template;
     }
 ​
 }

修改测试类中的RedisTemplate。再次进行测试

发现报错。

image-20250521192752154

发现是Jackson的依赖没有引入,因为在实际开发中springmvc的起步依赖中包括Jackson依赖。因此不用担心。

在将Jackson依赖手动导入后

 <dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-databind</artifactId>
 </dependency>

再次进行测试

测试成功

image-20250521193152120

查看redis数据库

image-20250521193130983

再次进行测试,看value中存入对象会如何将其序列化

添加User实体类

 package com.lyc.springbootredis.redis.pojo;
 ​
 public class User {
     private String name;
     private Integer age;
 ​
     public User() {
     }
 ​
     public User(String name, Integer age) {
         this.name = name;
         this.age = age;
     }
 ​
     public String getName() {
         return name;
     }
 ​
     public void setName(String name) {
         this.name = name;
     }
 ​
     public Integer getAge() {
         return age;
     }
 ​
     public void setAge(Integer age) {
         this.age = age;
     }
 ​
     @Override
     public String toString() {
         return "User{" +
                 "name='" + name + '\'' +
                 ", age=" + age +
                 '}';
     }
 }

编写测试类

 void testSaveUser(){
     User user = new User("星宇",18);
     //插入User类型数据
     redisTemplate.opsForValue().set("lyc:user:100",user);
     //获取User类型数据
     User user1 = (User)redisTemplate.opsForValue().get("lyc:user:100");
     System.out.println("user1 = " + user1);
 ​
 }

测试结果:

image-20250521200527382

检查redis数据库

image-20250521200618090

不仅将user对象序列化成JSON字符串,而且在获取数据时,又将JSON字符串反序列化成了Java对象。

是如何做到反序列化的呢?

RedisTemplate的Jackson序列化器将Java对象转换成json字符串时,在其中添加了@class属性,对应的就是User类的字节码名称,正是因为有这样一条属性,在反序列化时才能读取到对应的字节码名称,将数据库中的json字符串反序列化成对应的User对象。

尽管JSON的序列式方式可以满足我们的需求,但依然存在一些问题:

为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入JSON结果中,存入Redis,会带来额外的内存开销。

这个字节码本身占用的空间甚至比我们存储的数据加起来空间还大,每个对象都存这么多额外的信息(与业务无关),一旦对象数量一多,占用的内存空间会非常庞大,在实际开发中,大型项目的对象可能会超过成千上百万需要存储,这时再使用这种方式会浪费大量空间。

那如果不存储class类型的字节码数据呢?

如果不存储该字节码数据,RedisTemplate的自动序列化和反序列化就会失败,序列化时还可以,直接将对象里面的key与value存储进就好,但是反序列化需要知道json字符串要转成哪个类型的对象,如果不存储class类型的字节码数据,就无法识别到,就无法实现自动的反序列化。该字节码是实现反序列化的关键。

如果想要节省内存空间,就不能能去使用JSON序列化器来处理value,二是统一使用String序列化器,要求只能存储String类型的key或者value,当需要存储Java对象时,手动完成对象的序列化与反序列化。

如何手动序列化与反序列化?

手动序列化:将Java对象转换成json字符串的形式,在使用RedisTemplate内部方法传参

手动反序列化: 先通过RedisTemplate内部方法获取redis存储的值,在将其转换成java对象。

方案二:

Spring默认提供了一个StringRedisTemplate类,他的key与value的序列化方式默认是String方式,省去了自定义RedisTemplate的过程。

代码展示:

再次进行测试,这次使用StringRedisTemplate,并进行String类型存入,以及Java对象存入测试

 
@SpringBootTest
 class RedisStringTest {
     @Autowired
     private StringRedisTemplate  stringRedisTemplate;
     @Test
     void testString(){
         //插入String类型数据
         stringRedisTemplate.opsForValue().set("name","日月星辰");
         //获取String类型数据
         String name = stringRedisTemplate.opsForValue().get("name");
         System.out.println("name = "+name);
     }
     //JSON工具 springMVC默认使用的JSON处理工具
     private  static final  ObjectMapper objectMapper= new ObjectMapper();
     @Test
     void testSaveUser() throws Exception {
        //创建对象
         User user = new User("封不觉", 18);
         // 手动序列化
         String s = objectMapper.writeValueAsString(user);
         //插入User类型数据
         stringRedisTemplate.opsForValue().set("lyc:user:200",s);
         //获取User类型数据
         String s1 = stringRedisTemplate.opsForValue().get("lyc:user:200");
         //手动反序列化
         User user1 = objectMapper.readValue(s1, User.class);
         System.out.println("user1 = " + user1);
     }
 }

测试String类型结果:

image-20250522135506412

image-20250522135529654

测试User对象结果:

image-20250522135611325

image-20250522135626627

再次测试Hash类型的数据

代码展示:

 
void testHash(){
     //插入hash类型数据
     stringRedisTemplate.opsForHash().put("user:400","name","封不觉");
     stringRedisTemplate.opsForHash().put("user:400","age","18");
     //获取hash类型数据
     Map<Object, Object> map = stringRedisTemplate.opsForHash().entries("user:400");
     for (Map.Entry<Object, Object> entry : map.entrySet()){
         System.out.println(entry.getKey()+" = "+entry.getValue());
     }
 }

测试结果:

image-20250522140611888

image-20250522140631735

还有List,Set,SortedSet等可以自己测试下,都差不多

小结: 该方式大大减少了内存空间,而且仅仅加上了手动处理序列化及反序列化的代码,而且我们可以将其封装成工具类使用,与原来代码量相差无几,较为推荐。

希望对大家有所帮助!!


网站公告

今日签到

点亮在社区的每一天
去签到