Redis 序列化方案
1. 为什么需要关注序列化
Section titled “1. 为什么需要关注序列化”Redis 只能存储字节数组,Java 对象在存入 Redis 前必须序列化为字节,取出时再反序列化为对象。序列化方案的选择直接影响:
| 维度 | 影响 |
|---|---|
| 可读性 | Redis 管理台中能否直观查看数据 |
| 体积 | 影响内存占用和网络传输效率 |
| 性能 | 序列化/反序列化的 CPU 开销 |
| 兼容性 | 跨语言消费、类名变更后能否正常反序列化 |
2. 四种序列化方案对比
Section titled “2. 四种序列化方案对比”| 方案 | 实现类 | 可读性 | 体积 | 性能 | 跨语言 | 推荐度 |
|---|---|---|---|---|---|---|
| JDK 序列化 | JdkSerializationRedisSerializer | ❌ 乱码 | 最大 | 低 | ❌ | ❌ 不推荐 |
| String 序列化 | StringRedisSerializer | ✅ | 最小 | 最高 | ✅ | ✅ 纯字符串场景 |
| JSON 序列化 | Jackson2JsonRedisSerializer | ✅ | 小 | 高 | ✅ | ✅ 推荐 |
| GenericJackson2 | GenericJackson2JsonRedisSerializer | ✅ | 中(含类名) | 高 | ⚠️ | ✅ 多类型场景 |
3. JDK 序列化(默认,不推荐)
Section titled “3. JDK 序列化(默认,不推荐)”RedisTemplate 未配置时默认使用 JDK 序列化,将对象转为 Java 二进制字节流。
// 使用默认 RedisTemplate(JDK 序列化)redisTemplate.opsForValue().set("user:1", new User("Alice", 25));
// Redis 中实际存储:// Key: \xac\xed\x00\x05t\x00\x06user:1 ← key 也被序列化,带乱码前缀// Value: \xac\xed\x00\x05sr\x00\x04User... ← value 是二进制,不可读问题:
- key 包含乱码前缀,
KEYS user:*无法正确匹配 - value 不可读,无法在管理台调试
- 强依赖 Java,跨语言消费无法反序列化
- 类名/包名变更后反序列化失败
4. String 序列化
Section titled “4. String 序列化”只能存储字符串,适合 StringRedisTemplate 或作为 key 的序列化器。
// 存对象:手动 JSON 转换ObjectMapper objectMapper = new ObjectMapper();User user = new User("Alice", 25);stringRedisTemplate.opsForValue().set("user:1", objectMapper.writeValueAsString(user));
// 取对象:手动反序列化String json = stringRedisTemplate.opsForValue().get("user:1");User result = objectMapper.readValue(json, User.class);Redis 中存储:
Key: user:1 ← 干净可读Value: {"name":"Alice","age":25} ← 标准 JSON5. Jackson2JsonRedisSerializer(推荐)
Section titled “5. Jackson2JsonRedisSerializer(推荐)”使用 Jackson 将对象序列化为 JSON,需要指定具体类型。
5.1. 配置
Section titled “5.1. 配置”@Configurationpublic class RedisConfig {
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory);
// Key / HashKey 使用 String 序列化(保证 key 可读) StringRedisSerializer stringSerializer = new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer);
// Value / HashValue 使用 Jackson JSON 序列化 Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet(); return template; }}5.2. 类型陷阱与解决
Section titled “5.2. 类型陷阱与解决”// 存对象(自动序列化为 JSON)redisTemplate.opsForValue().set("user:1", new User("Alice", 25));
// ❌ 错误:直接强转失败,Jackson 反序列化 Object 类型返回 LinkedHashMapUser wrong = (User) redisTemplate.opsForValue().get("user:1");
// ✅ 正确:用 ObjectMapper 二次转换Object raw = redisTemplate.opsForValue().get("user:1");User correct = new ObjectMapper().convertValue(raw, User.class);6. GenericJackson2JsonRedisSerializer
Section titled “6. GenericJackson2JsonRedisSerializer”在 JSON 中自动写入 @class 类型信息,反序列化时自动还原为正确的 Java 类型。
@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer());
// 自动写入并读取类型信息 GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template;}Redis 中存储(含类型信息):
{ "@class": "com.example.dto.User", "name": "Alice", "age": 25}// 存/取无需手动指定类型redisTemplate.opsForValue().set("user:1", new User("Alice", 25));User user = (User) redisTemplate.opsForValue().get("user:1"); // 自动还原类型7. 推荐方案:StringRedisTemplate + 手动 JSON
Section titled “7. 推荐方案:StringRedisTemplate + 手动 JSON”生产环境最推荐:序列化过程完全透明,类型安全,无任何隐患。
7.1. 封装工具类
Section titled “7.1. 封装工具类”@Component@RequiredArgsConstructorpublic class RedisUtil {
private final StringRedisTemplate redisTemplate; private final ObjectMapper objectMapper;
public void set(String key, Object value, long ttl, TimeUnit unit) { try { redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(value), ttl, unit); } catch (JsonProcessingException e) { throw new RuntimeException("Redis 序列化失败", e); } }
// 单一类型反序列化 public <T> T get(String key, Class<T> type) { String json = redisTemplate.opsForValue().get(key); if (json == null) return null; try { return objectMapper.readValue(json, type); } catch (JsonProcessingException e) { throw new RuntimeException("Redis 反序列化失败", e); } }
// 泛型反序列化(如 List<User>) public <T> T get(String key, TypeReference<T> typeRef) { String json = redisTemplate.opsForValue().get(key); if (json == null) return null; try { return objectMapper.readValue(json, typeRef); } catch (JsonProcessingException e) { throw new RuntimeException("Redis 反序列化失败", e); } }
public void delete(String key) { redisTemplate.delete(key); } public Boolean hasKey(String key) { return redisTemplate.hasKey(key); } public void expire(String key, long ttl, TimeUnit unit) { redisTemplate.expire(key, ttl, unit); }}7.2. 使用示例
Section titled “7.2. 使用示例”// 单一类型User user = redisUtil.get("user:1001", User.class);
// 泛型列表List<User> users = redisUtil.get("user:list", new TypeReference<List<User>>() {});
// 存对象redisUtil.set("user:1001", user, 1, TimeUnit.HOURS);8. 选型速查
Section titled “8. 选型速查”存储类型? ├── 纯字符串(Token、验证码) │ └──▶ StringRedisTemplate 直接 set/get │ ├── Java 对象(项目内使用) │ └──▶ StringRedisTemplate + 手动 JSON(推荐)✅ │ ├── 多种类型混存(需自动还原类型) │ └──▶ GenericJackson2JsonRedisSerializer(接受 @class 开销) │ └── 跨语言消费(Python / Go 等) └──▶ StringRedisTemplate + 手动 JSON(标准 JSON,无 @class 污染)✅