看看序列化是怎么来的
先从 <String, String> 开始
java
redisTemplate.opsForValue().set("name", "Alice");
String value = redisTemplate.opsForValue().get("name");
从 Java 代码层面看,这很好理解:
set("name", "Alice"):把Alice存到 key 为name的位置get("name"):根据 keyname把值取出来
对应到 Redis 命令层面,可以理解为:
bash
SET name Alice
GET name
因为这里的 key 和 value 都是字符串,所以这个过程非常直观。
也正因为如此,<String, String> 是理解 RedisTemplate 数据转换的最好起点。
再看 <String, Object> 这个层面
如果现在 value 不再是字符串,而是一个对象,例如:
java
User user = new User(1L, "Alice", 18);
redisTemplate.opsForValue().set("user:1", user);
这时候就不能再简单理解成:
bash
SET user:1 User对象
因为 Redis 命令层面并不认识 Java 的 User 对象。
也就是说,对象不能直接原样放进 Redis,必须先转换成 Redis 能接受的形式。
最容易想到的一种方式,就是先把对象转成字符串,例如 JSON 字符串:
java
String userString = JSONObject.toJSONString(user);
redisTemplate.opsForValue().set("user:1", userString);
这样到了 Redis 命令层面,就更容易理解成:
bash
SET user:1 {"id":1,"name":"Alice","age":18}
所以,这一步的核心认识是:
String 类型之所以简单,是因为它本身就很接近 Redis 命令里的值。
Object 类型之所以复杂,是因为它必须先转换成某种可表示的形式。
为什么会引出"序列化"
当我们从 **<String, String>** 进入 **<String, Object>** 后,就会自然遇到这个问题:
- 字符串可以直接放,对象不能直接放,那对象要怎么变成 Redis 能存的内容?
这时候就需要一个"转换过程",这个过程本质上就是序列化,你可以直接这样理解:
- 序列化,就是把 Java 对象转换成 Redis 能接收、能存储的形式。
反过来,取值时再把它还原成 Java 对象,这就是反序列化。
一开始可以手动做序列化
最原始、最容易理解的做法,就是自己手动转:
java
String userString = JSONObject.toJSONString(user);
redisTemplate.opsForValue().set("user:1", userString);
取出来的时候再手动转回来:
java
String userString = redisTemplate.opsForValue().get("user:1");
User user = JSONObject.parseObject(userString, User.class);
这种做法的优点是:
- 容易理解
- 逻辑清晰
- 很适合初学阶段建立概念
但问题也很明显:
- 每次 set 都要手动
toJSONString - 每次 get 都要手动
parseObject - 重复代码很多
- 业务代码里混入了太多转换逻辑
所以会进一步封装 RedisUtils
为了避免每次都手动写转换代码,就会想到封装工具类,例如:
java
public void set(String key, Object obj) {
String str = JSONObject.toJSONString(obj);
redisTemplate.opsForValue().set(key, str);
}
这样做的意义是:把"对象转字符串"的重复逻辑,从业务代码中抽离出去。
业务层以后就不用反复写:
java
JSONObject.toJSONString(obj)
而是直接调用工具方法。
不过这里要注意一点:这仍然是你自己在 Java 代码里手动做转换,只不过把它封装起来了。
再后来,就会把序列化规则直接交给 RedisTemplate
现在抛弃封装 RedisUtils 方式,直接在 RedisTemplate 的配置类里,给 key 和 value 设置序列化方式。
这样之后,代码就可以直接写成:
java
redisTemplate.opsForValue().set("user:1", user);
这时候并不是 Redis 直接认识了 User 对象,而是:
- 你传进去的是
User RedisTemplate会按照你配置好的序列化器,先把它转换成指定格式- 然后再执行 Redis 操作
所以更准确的理解应该是:不是 Redis 能直接存 Java 对象了,而是 RedisTemplate 先帮我们把对象转换好了。
这就是为什么后面我们可以直接写:
java
redisTemplate.opsForValue().set("user:1", user);
而不用每次都手动写 JSONObject.toJSONString(user)。
不同场景会选不同的序列化方式
- JDK 序列化
:::color3
JDK 序列化通常是 Spring 体系里常见的默认方案之一。
它的特点是可以直接处理 Java 对象,但在 Redis 中存出来的结果通常不可读,看起来像乱码。
更准确地说,不是它"出了乱码",而是:JDK 序列化后的内容本来就不是给人直接阅读的文本。
所以你在 Redis 可视化工具里看时,会觉得它不可读。
:::
- JSON 序列化
:::color3
JSON 序列化会把对象转成 JSON 文本再存入 Redis。
它的好处是:
- 可读性强
- 调试方便
- 很适合大多数业务场景
所以很多项目里,key 用字符串序列化,value 用 JSON 序列化,是很常见的搭配。
:::
- 自定义序列化
:::color3
如果现成方案不能满足需求,也可以自己定义序列化规则。
也就是说,你自己决定:
- 对象怎么转成可存储内容
- 取出来时又怎么转回对象
这种方式灵活,但实现和维护成本更高。
:::
代码示例
value 采用 JDK 序列化
java
package com.example.demo.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.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisJdkConfig {
@Bean
public RedisTemplate<String, Object> jdkRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
StringRedisSerializer keySerializer = new StringRedisSerializer();
JdkSerializationRedisSerializer valueSerializer = new JdkSerializationRedisSerializer();
redisTemplate.setKeySerializer(keySerializer);
redisTemplate.setHashKeySerializer(keySerializer);
redisTemplate.setValueSerializer(valueSerializer);
redisTemplate.setHashValueSerializer(valueSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
value 采用 JSON 序列化
java
package com.example.demo.config;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
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.StringRedisSerializer;
@Configuration
public class RedisJsonConfig {
@Bean
public RedisTemplate<String, Object> jsonRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
StringRedisSerializer keySerializer = new StringRedisSerializer();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.activateDefaultTyping(
BasicPolymorphicTypeValidator.builder()
.allowIfSubType(Object.class)
.build(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
);
GenericJackson2JsonRedisSerializer valueSerializer =
new GenericJackson2JsonRedisSerializer(objectMapper);
redisTemplate.setKeySerializer(keySerializer);
redisTemplate.setHashKeySerializer(keySerializer);
redisTemplate.setValueSerializer(valueSerializer);
redisTemplate.setHashValueSerializer(valueSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
value 采用自定义序列化的代码示例
第一步:自定义 User类
java
package com.example.demo.model;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private Long id;
private String name;
private Integer age;
}
第二步:自定义序列化器
java
package com.example.demo.serializer;
import com.example.demo.model.User;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.StandardCharsets;
public class UserRedisSerializer implements RedisSerializer<User> {
@Override
public byte[] serialize(User user) throws SerializationException {
if (user == null) {
return new byte[0];
}
String value = user.getId() + "," + user.getName() + "," + user.getAge();
return value.getBytes(StandardCharsets.UTF_8);
}
@Override
public User deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length == 0) {
return null;
}
String value = new String(bytes, StandardCharsets.UTF_8);
String[] arr = value.split(",");
if (arr.length != 3) {
throw new SerializationException("User 反序列化失败,数据格式不正确: " + value);
}
User user = new User();
user.setId(Long.parseLong(arr[0]));
user.setName(arr[1]);
user.setAge(Integer.parseInt(arr[2]));
return user;
}
}
第三步:配置 RedisTemplate
java
package com.example.demo.config;
import com.example.demo.model.User;
import com.example.demo.serializer.UserRedisSerializer;
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 RedisCustomConfig {
@Bean
public RedisTemplate<String, User> customRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, User> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
StringRedisSerializer keySerializer = new StringRedisSerializer();
UserRedisSerializer valueSerializer = new UserRedisSerializer();
redisTemplate.setKeySerializer(keySerializer);
redisTemplate.setHashKeySerializer(keySerializer);
redisTemplate.setValueSerializer(valueSerializer);
redisTemplate.setHashValueSerializer(valueSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}