Redis 键值对序列化

看看序列化是怎么来的

先从 <String, String> 开始

java 复制代码
redisTemplate.opsForValue().set("name", "Alice");
String value = redisTemplate.opsForValue().get("name");

从 Java 代码层面看,这很好理解:

  • set("name", "Alice"):把 Alice 存到 key 为 name 的位置
  • get("name"):根据 key name 把值取出来

对应到 Redis 命令层面,可以理解为:

bash 复制代码
SET name Alice
GET name

因为这里的 key 和 value 都是字符串,所以这个过程非常直观。

也正因为如此,<String, String> 是理解 RedisTemplate 数据转换的最好起点。


再看 <String, Object> 这个层面

如果现在 value 不再是字符串,而是一个对象,例如:

java 复制代码
User user = new User(1L, "Alice", 18);
redisTemplate.opsForValue().set("user:1", user);

这时候就不能再简单理解成:

bash 复制代码
SET user:1 User对象

因为 Redis 命令层面并不认识 Java 的 User 对象。

也就是说,对象不能直接原样放进 Redis,必须先转换成 Redis 能接受的形式。

最容易想到的一种方式,就是先把对象转成字符串,例如 JSON 字符串:

java 复制代码
String userString = JSONObject.toJSONString(user);
redisTemplate.opsForValue().set("user:1", userString);

这样到了 Redis 命令层面,就更容易理解成:

bash 复制代码
SET user:1 {"id":1,"name":"Alice","age":18}

所以,这一步的核心认识是:

String 类型之所以简单,是因为它本身就很接近 Redis 命令里的值。

Object 类型之所以复杂,是因为它必须先转换成某种可表示的形式。


为什么会引出"序列化"

当我们从 **<String, String>** 进入 **<String, Object>** 后,就会自然遇到这个问题:

  • 字符串可以直接放,对象不能直接放,那对象要怎么变成 Redis 能存的内容?

这时候就需要一个"转换过程",这个过程本质上就是序列化,你可以直接这样理解:

  • 序列化,就是把 Java 对象转换成 Redis 能接收、能存储的形式。

反过来,取值时再把它还原成 Java 对象,这就是反序列化。


一开始可以手动做序列化

最原始、最容易理解的做法,就是自己手动转:

java 复制代码
String userString = JSONObject.toJSONString(user);
redisTemplate.opsForValue().set("user:1", userString);

取出来的时候再手动转回来:

java 复制代码
String userString = redisTemplate.opsForValue().get("user:1");
User user = JSONObject.parseObject(userString, User.class);

这种做法的优点是:

  • 容易理解
  • 逻辑清晰
  • 很适合初学阶段建立概念

但问题也很明显:

  • 每次 set 都要手动 toJSONString
  • 每次 get 都要手动 parseObject
  • 重复代码很多
  • 业务代码里混入了太多转换逻辑

所以会进一步封装 RedisUtils

为了避免每次都手动写转换代码,就会想到封装工具类,例如:

java 复制代码
public void set(String key, Object obj) {
    String str = JSONObject.toJSONString(obj);
    redisTemplate.opsForValue().set(key, str);
}

这样做的意义是:把"对象转字符串"的重复逻辑,从业务代码中抽离出去。

业务层以后就不用反复写:

java 复制代码
JSONObject.toJSONString(obj)

而是直接调用工具方法。

不过这里要注意一点:这仍然是你自己在 Java 代码里手动做转换,只不过把它封装起来了。


再后来,就会把序列化规则直接交给 RedisTemplate

现在抛弃封装 RedisUtils 方式,直接在 RedisTemplate 的配置类里,给 key 和 value 设置序列化方式。

这样之后,代码就可以直接写成:

java 复制代码
redisTemplate.opsForValue().set("user:1", user);

这时候并不是 Redis 直接认识了 User 对象,而是:

  • 你传进去的是 User
  • RedisTemplate 会按照你配置好的序列化器,先把它转换成指定格式
  • 然后再执行 Redis 操作

所以更准确的理解应该是:不是 Redis 能直接存 Java 对象了,而是 RedisTemplate 先帮我们把对象转换好了。

这就是为什么后面我们可以直接写:

java 复制代码
redisTemplate.opsForValue().set("user:1", user);

而不用每次都手动写 JSONObject.toJSONString(user)


不同场景会选不同的序列化方式

  1. JDK 序列化

:::color3

JDK 序列化通常是 Spring 体系里常见的默认方案之一。

它的特点是可以直接处理 Java 对象,但在 Redis 中存出来的结果通常不可读,看起来像乱码。

更准确地说,不是它"出了乱码",而是:JDK 序列化后的内容本来就不是给人直接阅读的文本。

所以你在 Redis 可视化工具里看时,会觉得它不可读。

:::

  1. JSON 序列化

:::color3

JSON 序列化会把对象转成 JSON 文本再存入 Redis。

它的好处是:

  • 可读性强
  • 调试方便
  • 很适合大多数业务场景

所以很多项目里,key 用字符串序列化,value 用 JSON 序列化,是很常见的搭配。

:::

  1. 自定义序列化

:::color3

如果现成方案不能满足需求,也可以自己定义序列化规则。

也就是说,你自己决定:

  • 对象怎么转成可存储内容
  • 取出来时又怎么转回对象

这种方式灵活,但实现和维护成本更高。

:::


代码示例

value 采用 JDK 序列化

java 复制代码
package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisJdkConfig {

    @Bean
    public RedisTemplate<String, Object> jdkRedisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        StringRedisSerializer keySerializer = new StringRedisSerializer();
        JdkSerializationRedisSerializer valueSerializer = new JdkSerializationRedisSerializer();

        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setHashKeySerializer(keySerializer);

        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashValueSerializer(valueSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

value 采用 JSON 序列化

java 复制代码
package com.example.demo.config;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisJsonConfig {

    @Bean
    public RedisTemplate<String, Object> jsonRedisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        StringRedisSerializer keySerializer = new StringRedisSerializer();

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.activateDefaultTyping(
            BasicPolymorphicTypeValidator.builder()
            .allowIfSubType(Object.class)
            .build(),
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY
        );

        GenericJackson2JsonRedisSerializer valueSerializer =
        new GenericJackson2JsonRedisSerializer(objectMapper);

        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setHashKeySerializer(keySerializer);

        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashValueSerializer(valueSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

value 采用自定义序列化的代码示例

第一步:自定义 User类
java 复制代码
package com.example.demo.model;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private Long id;
    private String name;
    private Integer age;
}
第二步:自定义序列化器
java 复制代码
package com.example.demo.serializer;

import com.example.demo.model.User;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.StandardCharsets;

public class UserRedisSerializer implements RedisSerializer<User> {

    @Override
    public byte[] serialize(User user) throws SerializationException {
        if (user == null) {
            return new byte[0];
        }
        String value = user.getId() + "," + user.getName() + "," + user.getAge();
        return value.getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public User deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length == 0) {
            return null;
        }

        String value = new String(bytes, StandardCharsets.UTF_8);
        String[] arr = value.split(",");

        if (arr.length != 3) {
            throw new SerializationException("User 反序列化失败,数据格式不正确: " + value);
        }

        User user = new User();
        user.setId(Long.parseLong(arr[0]));
        user.setName(arr[1]);
        user.setAge(Integer.parseInt(arr[2]));
        return user;
    }
}
第三步:配置 RedisTemplate
java 复制代码
package com.example.demo.config;

import com.example.demo.model.User;
import com.example.demo.serializer.UserRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisCustomConfig {

    @Bean
    public RedisTemplate<String, User> customRedisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, User> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        StringRedisSerializer keySerializer = new StringRedisSerializer();
        UserRedisSerializer valueSerializer = new UserRedisSerializer();

        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setHashKeySerializer(keySerializer);

        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashValueSerializer(valueSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
相关推荐
Lenyiin1 小时前
《LeetCode 顺序刷题》61 - 70
java·c++·python·算法·leetcode·lenyiin
敲代码的瓦龙2 小时前
Android?基础UI控件!!!
java·开发语言
Hesionberger2 小时前
LeetCode 78:子集生成全攻略
java·开发语言·数据结构·python·算法·leetcode·职场和发展
bzmK1DTbd2 小时前
Swagger API文档:Java RESTful服务的自动生成
java·开发语言·restful
G.晴天2 小时前
Linux常用命令练习流程
java·linux·运维·服务器·tomcat
与遨游于天地2 小时前
分布式锁从Redis到Redisson的演进
数据库·redis·分布式
身如柳絮随风扬2 小时前
Java对象在计算机中的执行原理:从JVM内存模型到对象创建全过程
java·开发语言·jvm
夕除2 小时前
spring boot
java·spring boot·后端