Redis-RedisTemplate写入Redis数据键值出现/x00等前缀

  1. 1. springboot使用Jedis
    1. 1.1. 1.pom引入依赖
    2. 1.2. 2.编写RedisConfig
  2. 2. 导入依赖:
  3. 3. 配置文件:
  4. 4. java代码:
  5. 5. 问题
  6. 6. 怎么产生的
  7. 7. 解决方法:
    1. 7.1. 测试:
      1. 7.1.1. User.java
      2. 7.1.2. RedisConfig.java
      3. 7.1.3. 截图:
  8. 8. StringRedisTemplate
  9. 9. 总结

springboot使用Jedis

​ 在springboot2.X版本后,默认使用的的redis客户端是Lettuce,如果项目中想要使用jedis,需要先把Lettuce依赖去掉,一般如下步骤

1.pom引入依赖

引入spring-boot-starter-data-redis
排除lettuce-core
引入jedis

2.编写RedisConfig

注入Jedis相关类,如JedisPoolConfig,RedisStandaloneConfiguration,JedisConnectionFactory,RedisTemplate

注意序列化问题
在创建RedisTemplate的时,如果项目使用比较简单,redis存储的是字符串,可以直接使用StringRedisTemplate,它用的是字符串的序列化器,如果redis想存储对象,则需要其他的序列化反序列化器,下面代码例子使用的是一个json的序列化器Jackson2JsonRedisSerializer

3.使用

导入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!-- 如果使用jedis,则去掉默认的lettuce,再引入redis client-->
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

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

<!--连接池依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 应用名称
spring:
application:
name: redis-springboot
redis:
host: 192.168.72.30
port: 6379
password: 123321
# spring默认使用 lettuce
jedis:
pool:
max-active: 8
min-idle: 0
max-wait: 100ms

java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootTest
class RedisSpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;

@Test
void testString() {
// 1. 通过RedisTemplate获取操作的String类型的valueOperations对象
ValueOperations ops = redisTemplate.opsForValue();
// 2. 插入一条数据
ops.set("name", "远哥");
// 3. 获取数据
String name = (String) ops.get("name");
System.out.println("name="+name);
}
}

问题

怎么产生的

​ 当我们的数据存储到Redis的时候,我们的键(key)和值(value)都是通过Spring提供的Serializer序列化到数据库的。RedisTemplate默认使用的是JdkSerializationRedisSerializer,StringRedisTemplate默认使用的是StringRedisSerializer。

​ JdkSerializationRedisSerializer: 使用JDK提供的序列化功能。优点是反序列化时不需要提供类型信息(class),但缺点是需要实现Serializable接口,还有序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。

RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化

缺点:

  • 可读性差
  • 内存占用较大

解决方法:

​ RedisTemplate中 value 的序列化,比较常用的是JdkSerializationRedisSerializer和GenericJackson2JsonRedisSerializer。
​ 前者需要对象实现java.io.Serializable接口,并且序列化后结果较大,不易阅读,所以更推荐采用GenericJackson2JsonRedisSerializer方式。

测试:

User.java

1
2
3
4
5
6
7
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private Integer age;
}

RedisConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 1.创建RedisTemplate对象
RedisTemplate<String ,Object> redisTemplate = new RedisTemplate<>();
// 2.设置连接工厂
redisTemplate.setConnectionFactory(factory);

// 3.创建序列化对象
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

// 4.设置key和hashKey采用String的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);

// 5.设置value和hashValue采用json的序列化方式
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);

return redisTemplate;
}

}
1
2
3
4
5
6
@Test
void testSaveUser() {
redisTemplate.opsForValue().set("user:100", new User("Vz", 21));
User user = (User) redisTemplate.opsForValue().get("user:100");
System.out.println("User = " + user);
}

保存数据成功!

截图:

尽管Json序列化可以满足我们的需求,但是依旧存在一些问题。

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

那么我们如何解决这个问题呢?我们可以通过下文的StringRedisTemplate来解决这个问题。

如上图保存的json数据: “@class”: “com.example.redisspringboot.domain.User”

StringRedisTemplate


为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。

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

  • 我们可以直接编写一个测试类使用StringRedisTemplate来执行以下方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@SpringBootTest
public class RedisStringTemplateTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;

@Test
void testSaveUser() throws JsonProcessingException {
// 1.创建一个Json序列化对象
ObjectMapper objectMapper = new ObjectMapper();
// 2.将要存入的对象通过Json序列化对象转换为字符串
String userJson1 = objectMapper.writeValueAsString(new User("Vz", 21));
// 3.通过StringRedisTemplate将数据存入redis
stringRedisTemplate.opsForValue().set("user:100",userJson1);
// 4.通过key取出value
String userJson2 = stringRedisTemplate.opsForValue().get("user:100");
// 5.由于取出的值是String类型的Json字符串,因此我们需要通过Json序列化对象来转换为java对象
User user = objectMapper.readValue(userJson2, User.class);
// 6.打印结果
System.out.println("user = " + user);
}

}

此时已经没有了@class

总结


RedisTemplate的两种序列化实践方案,两种方案各有各的优缺点,可以根据实际情况选择使用。

方案一:

  1. 自定义RedisTemplate
  2. 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer

方案二:

  1. 使用StringRedisTemplate
  2. 写入Redis时,手动把对象序列化为JSON
  3. 读取Redis时,手动把读取到的JSON反序列化为对象