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。
如果想要使用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);
}
}
测试结果:
注意事项:
在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中我们可以在其中看到对应的序列化器
由此引出 :
SpringDataRedis的序列化方式
RedisTemplate可以接受任意Object作为值写入Redis,只不过写入前会将Object序列化成字节形式,默认是采用JDK的序列化(ObjectOutputStream)得到结果如下:
缺点:
可读性差 (原本以为修改了name,没想到是新建了一个key,会导致之后的逻辑错误)。
内存占用较大。
如何修改,做到所见即所得呢?
只能去修改redisTemplate的序列化方式。
我们可以找到RedisSerializer接口去看他的实现类。
我们可以看到默认使用的序列化器JDKSerializationRedisSerializer类,不是很好用。
然后还有专门处理字符串的StringRedisSerializer类,字符串转字节写入Redis只需要getbytes()方法即可,不需要去使用JDK序列化器 ,而StringRedisSerializer类底层就是使用getBytes()方法。
使用场景:在key与HashKey都为字符串是可以使用。一般情况下key都为字符串,只有value可能是对象。
如果value是对象,则建议使用Jackson2JsonRedisSerializer类,也就是转json字符串的序列化工具。
总结来说:我们的key一般使用StringRedisSerializer,我们的value一般使用Jackson2JsonRedisSerializer类型。
要修改什么样的序列化方式清楚了,那应该怎样转换呢?
方案一:
我们可以自定义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。再次进行测试
发现报错。
发现是Jackson的依赖没有引入,因为在实际开发中springmvc的起步依赖中包括Jackson依赖。因此不用担心。
在将Jackson依赖手动导入后
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
再次进行测试
测试成功
查看redis数据库
再次进行测试,看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);
}
测试结果:
检查redis数据库
不仅将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类型结果:
测试User对象结果:
再次测试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());
}
}
测试结果:
还有List,Set,SortedSet等可以自己测试下,都差不多
小结: 该方式大大减少了内存空间,而且仅仅加上了手动处理序列化及反序列化的代码,而且我们可以将其封装成工具类使用,与原来代码量相差无几,较为推荐。
希望对大家有所帮助!!