Spring Data Redis 实战避坑:搞定序列化乱码与 Hash 结构存储
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:
@ConfigurationpublicclassRedisConfig{@Bean@SuppressWarnings({"rawtypes","unchecked"})publicRedisTemplate<String,Object>redisTemplate(RedisConnectionFactoryfactory){RedisTemplate<String,Object>template=newRedisTemplate<>();template.setConnectionFactory(factory);// 1. 配置 Value 的 JSON 序列化Jackson2JsonRedisSerializer<Object>jackson2JsonRedisSerializer=newJackson2JsonRedisSerializer<>(Object.class);ObjectMapperom=newObjectMapper();om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);om.activateDefaultTyping(om.getPolymorphicTypeValidator(),ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 2. 配置 Key 的 String 序列化StringRedisSerializerstringRedisSerializer=newStringRedisSerializer();// 3. 注入序列化器template.setKeySerializer(stringRedisSerializer);template.setHashKeySerializer(stringRedisSerializer);template.setValueSerializer(jackson2JsonRedisSerializer);template.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();returntemplate;}}经过这番配置,你再存入数据时,Key 就是清清爽爽的字符串,而 Value 也会变成可读性极强的 JSON 格式。
三、 另一种选择:StringRedisTemplate
如果你觉得自定义序列化配置太麻烦,或者你的业务场景中缓存的大多是简单的字符串(比如 Token、验证码),Spring Boot 其实还提供了一个开箱即用的
StringRedisTemplate。
它的 Key 和 Value 默认都采用 String 序列化。如果你需要存储 Java 对象,只需要在写入前手动将对象序列化为 JSON 字符串,读取时再手动反序列化即可:
个人在学习中常用此方法
@AutowiredprivateStringRedisTemplatestringRedisTemplate;@AutowiredprivateObjectMapperobjectMapper;// 存入对象publicvoidsaveUser(Useruser)throwsException{Stringjson=objectMapper.writeValueAsString(user);stringRedisTemplate.opsForValue().set("user:"+user.getId(),json,30,TimeUnit.MINUTES);}// 取出对象publicUsergetUser(Stringid)throwsException{Stringjson=stringRedisTemplate.opsForValue().get("user:"+id);returnjson!=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) |
代码示例:
@AutowiredprivateRedisTemplate<String,Object>redisTemplate;publicvoidtestHash(){// 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)Objectname=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,就能轻松搞定对象字段的增删改查。
