杰瑞科技汇

Java序列化后如何存入Redis?

  1. 存入 Redis:将 Java 对象转换为字节流(序列化),然后将这个字节流作为 value 存储到 Redis 的 StringHash 等结构中。
  2. 从 Redis 取出:从 Redis 中获取字节流,然后将这个字节流转换回原来的 Java 对象(反序列化)。

下面我将从 基本原理、常用序列化方式、完整代码示例、最佳实践 四个方面进行详细说明。

Java序列化后如何存入Redis?-图1
(图片来源网络,侵删)

基本原理

Java 序列化是将 Java 对象的状态信息转换为可以存储或传输的形式(通常是字节流)的过程,这个过程需要对象实现 java.io.Serializable 接口,反序列化则是相反的过程,将字节流恢复成 Java 对象。

当使用 Redis 客户端(如 Jedis, Lettuce)操作 Redis 时,客户端会自动处理序列化和反序列化,我们通常需要配置一个 Serializer 接口的实现,告诉客户端如何进行转换。

org.springframework.data.redis.serializer 包中定义了几个常用的序列化器:

  • JdkSerializationRedisSerializer: 使用 Java 原生的序列化机制,优点是无需额外依赖,缺点是序列化后体积大、可读性差,且可能存在安全问题。
  • StringRedisSerializer: 将 String 类型的 keyvalue 进行序列化,它实际上是 UTF-8 编码的转换器,最常用,最安全。
  • GenericJackson2JsonRedisSerializer: 使用 Jackson 库将对象序列化为 JSON 字符串,优点是可读性好,跨语言,体积相对较小,是目前最推荐的方案之一。
  • OxmSerializer: 使用 XML 进行序列化,较少使用。

常用序列化方式对比

序列化方式 优点 缺点 适用场景
JDK 原生 无需额外依赖
Java 生态内兼容性好
序列化后体积大
可读性差(二进制)
性能相对较低
跨版本可能不兼容
项目内部使用,对性能和体积要求不高的场景。
JSON 可读性好,便于调试
跨语言,通用性强
序列化后体积相对较小
需要引入 Jackson/Gson 等依赖
无法直接序列化复杂对象(如循环引用)
反序列化时需要目标类的无参构造器
强烈推荐,大多数 Web 应用、微服务场景的首选。
String 性能极高
可读性最好
兼容性最强
只能处理 String 类型 存储 key 或简单的 value,如 session:123
Protostuff/Kryo 序列化速度极快,体积极小
高性能序列化框架
需要额外依赖
可读性差
对性能和存储空间有极致要求的场景,如缓存大量数据。

完整代码示例 (以 Spring Boot + Redis + Jackson 为例)

这是目前最主流和推荐的方案,我们将使用 GenericJackson2JsonRedisSerializer

Java序列化后如何存入Redis?-图2
(图片来源网络,侵删)

步骤 1: 项目依赖 (pom.xml)

确保你的 pom.xml 中包含 Spring Boot Data Redis 和 Jackson 依赖。

<dependencies>
    <!-- Spring Boot Starter Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- Lombok (可选,简化代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!-- Spring Boot Web (可选) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

步骤 2: 配置 Redis 连接 (application.yml)

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    # password: yourpassword # 如果有密码
    database: 0
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0

步骤 3: 配置序列化器

创建一个配置类,明确指定 keyvalue 的序列化方式。

  • key 使用 StringRedisSerializer,保证可读性。
  • value 使用 GenericJackson2JsonRedisSerializer,支持复杂对象。
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 RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        // 1. 创建 RedisTemplate 对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        // 2. 创建序列化器
        // Key 使用 String 序列化
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        // Value 使用 JSON 序列化
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
        // 3. 设置 Key 和 Value 的序列化器
        template.setKeySerializer(stringSerializer);
        template.setValueSerializer(jsonSerializer);
        template.setHashKeySerializer(stringSerializer);
        template.setHashValueSerializer(jsonSerializer);
        // 4. 初始化 RedisTemplate
        template.afterPropertiesSet();
        return template;
    }
}

步骤 4: 创建可序列化的 Java 对象

要被序列化的类 必须有无参构造函数,并且最好实现 Serializable 接口(虽然 Jackson 不强制,但这是一个好习惯)。

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor // Jackson 反序列化需要
@AllArgsConstructor
public class User implements Serializable {
    private Long id;
    private String username;
    private String email;
    private LocalDateTime createTime;
}

步骤 5: 编写 Service 进行操作

注入 RedisTemplate 并使用它来存取对象。

Java序列化后如何存入Redis?-图3
(图片来源网络,侵删)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    private static final String USER_KEY_PREFIX = "user:";
    /**
     * 保存用户信息到 Redis
     * @param user 用户对象
     */
    public void saveUser(User user) {
        String key = USER_KEY_PREFIX + user.getId();
        // opsForValue().set 会自动调用配置的 GenericJackson2JsonRedisSerializer 进行序列化
        redisTemplate.opsForValue().set(key, user, 10, TimeUnit.MINUTES);
        System.out.println("用户信息已存入 Redis: " + key);
    }
    /**
     * 从 Redis 获取用户信息
     * @param id 用户ID
     * @return 用户对象,如果不存在则返回 null
     */
    public User getUser(Long id) {
        String key = USER_KEY_PREFIX + id;
        // get 会自动调用配置的 GenericJackson2JsonRedisSerializer 进行反序列化
        Object obj = redisTemplate.opsForValue().get(key);
        if (obj instanceof User) {
            return (User) obj;
        }
        return null;
    }
}

步骤 6: 测试

你可以写一个单元测试或 Controller 来调用 UserService 的方法。

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class UserServiceTest {
    @Autowired
    private UserService userService;
    @Test
    public void testSaveAndGetUser() {
        User user = new User(1L, "张三", "zhangsan@example.com", LocalDateTime.now());
        // 保存
        userService.saveUser(user);
        // 获取
        User retrievedUser = userService.getUser(1L);
        System.out.println("从 Redis 取出的用户: " + retrievedUser);
        // 验证
        assert user.getId().equals(retrievedUser.getId());
        assert user.getUsername().equals(retrievedUser.getUsername());
    }
}

运行结果分析: 当你用 Redis CLI 连接到 Redis 并查看数据时,你会看到类似这样的内容(可读的 JSON 格式):

0.0.1:6379> get user:1
"{\"@class\":\"com.example.demo.User\",\"id\":1,\"username\":\"张三\",\"email\":\"zhangsan@example.com\",\"createTime\":\"2025-10-27T10:30:00\"}"

注意 @class 字段,这是 Jackson2JsonRedisSerializer 添加的,它记录了原始对象的类全限定名,这样在反序列化时,Jackson 就能知道应该将 JSON 数据转换回哪个类的实例。


最佳实践与注意事项

  1. 选择合适的序列化器

    • 优先选择 GenericJackson2JsonRedisSerializer:它在可读性、性能和功能之间取得了很好的平衡。
    • key 始终使用 StringRedisSerializer,保证 key 的可读性和通用性。
    • 避免直接使用 JdkSerializationRedisSerializer,除非你有特殊原因。
  2. 处理缓存穿透、击穿、雪崩

    • 穿透:查询一个不存在的数据,解决方案:缓存空对象(null)或使用布隆过滤器。
    • 击穿:一个热点key突然失效,大量请求直接打到数据库,解决方案:设置合理的过期时间,使用互斥锁(如 Redis 的 SETNX)。
    • 雪崩:大量key在同一时间集体失效,解决方案:给key的过期时间加上随机值。
  3. Redis 数据结构选择

    • 存储单个对象:使用 String 结构,key 是唯一标识,value 是序列化后的对象。
    • 存储对象列表:可以使用 List 结构,将多个对象序列化后存入一个列表。
    • 存储对象集合:可以使用 Set 结构。
    • 存储对象属性:如果对象很大但只需要更新部分属性,可以考虑使用 Hash 结构,field 是属性名,value 是属性值。
  4. 反序列化时的类型安全redisTemplate.getForObject() 方法提供了更直接的类型转换,可以避免手动 instanceof 判断。

   // 替代方案
   public User getUserWithGetForObject(Long id) {
       String key = USER_KEY_PREFIX + id;
       // 直接指定返回类型,RedisTemplate 会自动处理类型转换
       return redisTemplate.opsForValue().get(key, User.class);
   }
  1. 版本兼容性: 如果你使用 JdkSerializationRedisSerializer,要特别注意应用版本升级时,Java 类的 serialVersionUID 变化会导致反序列化失败,使用 JSON 序列化可以避免这个问题。
分享:
扫描分享到社交APP
上一篇
下一篇