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学习笔记十五(GUI)
java·笔记·学习
落木萧萧82519 分钟前
MyBatis 关联查询的四种写法,为什么最后都变回了手写 XML
java·数据库·后端
csdn2015_19 分钟前
java springboot项目,接收到的参数多个逗号
java·开发语言·spring boot
cui_hao_nan20 分钟前
Spring AOP 自调用、代理与 private 方法引发的诡异 NPE
java·后端·spring·ai开发
码艺-Alimjan30 分钟前
维吾尔语数字转文本
java·javascript·python·算法·go
事圆则缓36 分钟前
Java 常见数据结构与 Android 使用场景
android·java·数据结构
xieliyu.1 小时前
计算机网络:Fiddler 抓包工具使用教程 + HTTP 协议报文格式详解
java·笔记·计算机网络·测试工具·http·java-ee·fiddler
步行cgn1 小时前
@Value 详解:Spring 属性注入的核心注解
java·python·spring
2401_850481171 小时前
UVA815
java
霸道流氓气质1 小时前
Spring AI 多租户隔离方案
java·人工智能·spring