Spring Data Redis 实战避坑:搞定序列化乱码与 Hash 结构存储
在 Java 后端开发中,Redis 几乎是高并发场景下的标配。虽然 Redis 自带的命令行客户端
redis-cli功能强大,但在实际项目中,我们更多是通过 Java 客户端来与 Redis 进行交互。下面将讲述最容易踩到的"序列化乱码"深坑,以及如何优雅地使用 Hash 结构存储对象。
一、 从 Jedis 到 Spring Data Redis
在 Java 生态中,操作 Redis 的主流客户端有 Jedis 和 Lettuce。早期的 Jedis 实例是线程不安全的,为了避免频繁创建和销毁 TCP 连接带来的性能损耗,我们通常需要引入 Jedis 连接池(JedisPool)。
而 Spring Data Redis 作为 Spring 家族的一员,对上述客户端进行了高度封装。它提供了统一的 API ------
RedisTemplate,不仅屏蔽了底层客户端的差异,还支持 Redis 哨兵、集群以及响应式编程。
在 Spring Boot 项目中,我们只需要引入 spring-boot-starter-data-redis 和连接池依赖 commons-pool2,就可以在 YAML 中配置好 Redis 的连接信息,通过 @Autowired 直接注入 RedisTemplate 来使用。
二、 避坑指南:序列化导致的"乱码"危机
RedisTemplate 虽然好用,但很多新手在第一次使用时都会遇到一个诡异的现象:明明在 Java 代码中设置了一个 Key,但在 Redis 图形化客户端中查看时,Key 前面却多了一长串看不懂的乱码,或者存入的 Value 变成了一串二进制字节。

这是因为 RedisTemplate 默认采用了 JDK 序列化(JdkSerializationRedisSerializer)。 这种序列化方式虽然能保留对象的完整结构,但可读性极差,且占用的内存空间较大。
为了解决这个问题,我们需要自定义 RedisTemplate 的序列化策略。通常的最佳实践是:Key 采用 String 序列化,Value 采用 JSON 序列化。
我们可以通过以下配置类,将序列化方式替换为 Jackson2JsonRedisSerializer:
java
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings({"rawtypes", "unchecked"})
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 1. 配置 Value 的 JSON 序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 2. 配置 Key 的 String 序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 3. 注入序列化器
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
经过这番配置,你再存入数据时,Key 就是清清爽爽的字符串,而 Value 也会变成可读性极强的 JSON 格式。
三、 另一种选择:StringRedisTemplate
如果你觉得自定义序列化配置太麻烦,或者你的业务场景中缓存的大多是简单的字符串(比如 Token、验证码),Spring Boot 其实还提供了一个开箱即用的
StringRedisTemplate。
它的 Key 和 Value 默认都采用 String 序列化。如果你需要存储 Java 对象,只需要在写入前手动将对象序列化为 JSON 字符串,读取时再手动反序列化即可:
个人在学习中常用此方法
java
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ObjectMapper objectMapper;
// 存入对象
public void saveUser(User user) throws Exception {
String json = objectMapper.writeValueAsString(user);
stringRedisTemplate.opsForValue().set("user:" + user.getId(), json, 30, TimeUnit.MINUTES);
}
// 取出对象
public User getUser(String id) throws Exception {
String json = stringRedisTemplate.opsForValue().get("user:" + id);
return json != null ? objectMapper.readValue(json, User.class) : null;
}
四、 Redis Hash 与 Java 代码的映射实战
除了基础的 String 类型,Redis 的 Hash 结构非常适合用来存储对象(比如用户信息、商品信息)。它允许我们将一个对象的多个字段拆分开来存储,而不是像 String 那样必须把整个对象序列化成一个大 JSON。
在 Redis 命令行中,我们使用 HSET 和 HGET 来操作 Hash。而在 Java 的 RedisTemplate 中,这些操作被封装在了 HashOperations 接口里。
以下是 Redis 命令与 Java API 的对照实战:
| 操作场景 | Redis 命令 | Java (HashOperations) |
|---|---|---|
| 添加/更新字段 | HSET key field value |
put(key, hashKey, value) |
| 获取单个字段 | HGET key field |
get(key, hashKey) |
| 获取所有字段 | HGETALL key |
entries(key) |
| 删除字段 | HDEL key field |
delete(key, hashKey) |
代码示例:
java
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void testHash() {
// 1. 获取 Hash 操作对象
HashOperations<String, Object, Object> opsForHash = redisTemplate.opsForHash();
// 2. 存入用户信息 (相当于 HSET user:1001 name Tom)
opsForHash.put("user:1001", "name", "Tom");
opsForHash.put("user:1001", "age", 25);
// 3. 获取单个字段 (相当于 HGET user:1001 name)
Object name = opsForHash.get("user:1001", "name");
System.out.println("用户名: " + name);
// 4. 获取所有字段 (相当于 HGETALL user:1001)
Map<Object, Object> entries = opsForHash.entries("user:1001");
System.out.println("用户所有信息: " + entries);
}
五、 总结
- 如果你需要存储复杂的对象且希望 Redis 中数据可读,推荐使用自定义序列化器的
RedisTemplate。 - 如果你只是存储简单的字符串,
StringRedisTemplate配合手动序列化是更轻量级的选择。 - 在操作 Hash 结构时,记住
HSET对应put,HGETALL对应entries,就能轻松搞定对象字段的增删改查。