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

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 命令行中,我们使用 HSETHGET 来操作 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 对应 putHGETALL 对应 entries,就能轻松搞定对象字段的增删改查。
相关推荐
吴声子夜歌11 小时前
Java——线程的中断
java·中断
吴声子夜歌11 小时前
状态机——SpringStateMachine嵌套状态流转
java·状态机·嵌套状态
Jul1en_11 小时前
【SpringCloud】微服务 Sentinel 详解
java·spring·sentinel
phltxy11 小时前
Redis 常见面试题
数据库·redis·缓存
闪电悠米11 小时前
黑马点评短信登录01_session_sms_login
java·spring boot·redis·git·spring·面试
Advancer-11 小时前
黑马点评plus --异步秒杀重构升级
java·spring boot·重构·intellij-idea
Dicky-_-zhang11 小时前
服务网格实战:Istio与Linkerd对比选型与落地实践
java·jvm
云烟成雨TD11 小时前
Spring AI Alibaba 1.x 系列【56】SAA Admin 平台功能介绍
java·人工智能·spring
Gauss松鼠会11 小时前
GaussDB(DWS) 资源监控Topsql
java·网络·数据库·算法·oracle·性能优化·gaussdb