Spring Data Redis对象缓存序列化问题

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 在使用 Redis 时,有没有遇到同我一样,对象缓存序列化问题的呢?

相信在项目中,你一定是经常使用 Redis ,那么,你是怎么使用的呢?在使用时,有没有遇到同我一样,对象缓存序列化问题的呢?那么,你又是如何解决的呢?

Redis 使用示例

添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

在应用启动如何添加启用缓存注解(@EnableCaching)。

假如我们有一个用户对象(UserVo):

@Data
public class UserVo implements Serializable {
   
   

    @Serial
    private static final long serialVersionUID = 2215423070276994378L;

    private Long id;

    private String name;

    private LocalDateTime createDateTime;

}

这里,我们实现了 Serializable 接口。

在我们需要缓存的方法上,使用 @Cacheable 注解,就表示如果返回的对象不是 null 时,就会对其进行缓存,下次查询,首先会去缓存中查询,查到了,就直接返回,不会再去数据库查询,查不到,再去数据库查询。

@Service
@Slf4j
public class UserServiceImpl implements IUserService {
   
   

    @Override
    @Cacheable(
            value = "sample-redis",
            key = "'user-'+#id",
            unless = "#result == null"
    )
    public UserVo getUserById(Long id) {
   
   

        log.info("userVo from db query");

        UserVo userVo = new UserVo();
        userVo.setId(1L);
        userVo.setName("Zhang San");
        userVo.setCreateDateTime(LocalDateTime.now());

        return userVo;
    }

}

核心代码:

@Cacheable(
        value = "sample-redis",
        key = "'user-'+#id",
        unless = "#result == null"
)

模拟测试,再写一个测试接口:

@RestController
@RequestMapping("/sample")
@RequiredArgsConstructor
@Slf4j
public class SampleController {
   
   

    private final IUserService userService;

    @GetMapping("/user/{id}")
    public UserVo getUserById(@PathVariable Long id) {
   
   

        UserVo vo = userService.getUserById(id);

        log.info("vo: {}", JacksonUtils.json(vo));

        return vo;
    }

}

我们再加上连接 redis 的配置:

spring:
  data:
    redis:
      host: localhost
      port: 6379

测试:

### getUserById
GET http://localhost:8080/sample/user/1

image-20231229232659949

输出结果跟我们想的一样,第一次从数据库查,后面都从缓存直接返回。

总结一下:

  1. 添加 spring-boot-starter-data-redis 依赖。

  2. 使用启用缓存注解(@EnableCaching)。

  3. 需要缓存的对象实现 Serializable 接口。
  4. 使用 @Cacheable 注解缓存查询的结果。

遇到问题

在上面我们通过 spring boot 提供的 redis 实现了查询对象缓存这样一个功能,有下面几个问题:

  1. 缓存的对象,必须序列化,不然会报错。
  2. redis 存储的数据,看不懂,可以转成 json 格式吗?
  3. 使用 Jackson 时,遇到特殊类型的字段会报错,比如 LocalDateTime。

第1个问题,如果对象没有实现 Serializable接口,会报错:

image-20231230230844373

关键信息:

java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [xxx.xxx.UserVo]

我详细描述一下第3个问题,默认是使用 Jdk序列化 JdkSerializationRedisSerializer,redis 里面存的数据如下:

image-20231229232824285

问题很明显,对象必须要实现序列化接口,存的数据不易查看,所以,改用 GenericJackson2JsonRedisSerializer ,这就有了第3个问题。

我们加上下面的配置,就能解决第2个问题。

@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
   
   
    return RedisCacheConfiguration
            .defaultCacheConfig()
            .serializeValuesWith(
                    RedisSerializationContext
                            .SerializationPair
                            .fromSerializer(RedisSerializer.json())
            );
}

下面看第三个问题的错误:

image-20231229233716885

如何解决?

既然有了明确的错误提示,那也是好解决的,我们可以这样:

@JsonDeserialize(using = LocalDateTimeDeserializer.class)        // 反序列化
@JsonSerialize(using = LocalDateTimeSerializer.class)            // 序列化
private LocalDateTime createDateTime;

这样就可以了,我们看下redis里面存的数据:

{
   
   "@class":"com.fengwenyi.erwin.component.sample.redis.vo.UserVo","id":1,"name":"Zhang San","createDateTime":[2023,12,29,23,44,3,479011000]}

其实到这里,已经解决了问题,那有没有更省心的办法呢?

解决办法

其实我们知道,使用的就是 Jackson 进行 json 转换,而 json 转换,遇到 LocalDateTime 问题时,我们配置一下 module 就可以了,因为默认用的 SimpleModule,我们改用 JavaTimeModule 就可以了。

这时候问题又来啦,错误如下:

image-20231229233248619

这时候存的数据如下:

{
   
   "id":1,"name":"Zhang San","createDateTime":"2023-12-29T23:31:52.548517"}

这就涉及到 Jackson 序列化漏洞的问题了,采用了白名单机制,我们就粗暴一点:

jsonMapper.activateDefaultTyping(
  LaissezFaireSubTypeValidator.instance, 
  ObjectMapper.DefaultTyping.NON_FINAL
);

redis 存的数据如下:

["com.fengwenyi.erwin.component.sample.redis.vo.UserVo",{
   
   "id":1,"name":"Zhang San","createDateTime":"2023-12-29T23:56:18.197203"}]

最后,来一段完整的 RedisCacheConfiguration 配置代码:

@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
   
   
    return RedisCacheConfiguration
            .defaultCacheConfig()
            .serializeValuesWith(
                    RedisSerializationContext
                            .SerializationPair
//                            .fromSerializer(RedisSerializer.json())
//                            .fromSerializer(
//                                    new GenericJackson2JsonRedisSerializer()
//                            )
                            .fromSerializer(redisSerializer())
            );
}

private RedisSerializer<Object> redisSerializer() {
   
   
    JsonMapper jsonMapper = new JsonMapper();
    JacksonUtils.configure(jsonMapper);
    jsonMapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance, 
            ObjectMapper.DefaultTyping.NON_FINAL
    );
    return new GenericJackson2JsonRedisSerializer(jsonMapper);
}

希望今天的分享对你有一定的帮助。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
17天前
|
消息中间件 NoSQL Java
Spring Boot整合Redis
通过Spring Boot整合Redis,可以显著提升应用的性能和响应速度。在本文中,我们详细介绍了如何配置和使用Redis,包括基本的CRUD操作和具有过期时间的值设置方法。希望本文能帮助你在实际项目中高效地整合和使用Redis。
36 1
|
24天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
80 2
|
29天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
1月前
|
JSON 前端开发 数据格式
前端的全栈之路Meteor篇(五):自定义对象序列化的EJSON介绍 - 跨设备的对象传输
EJSON是Meteor框架中扩展了标准JSON的库,支持更多数据类型如`Date`、`Binary`等。它提供了序列化和反序列化功能,使客户端和服务器之间的复杂数据传输更加便捷高效。EJSON还支持自定义对象的定义和传输,通过`EJSON.addType`注册自定义类型,确保数据在两端无缝传递。
|
1月前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
85 2
|
1月前
|
存储 Java API
如何使用 Java 记录简化 Spring Data 中的数据实体
如何使用 Java 记录简化 Spring Data 中的数据实体
38 9
|
1月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
22天前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
34 0
|
1月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第3天】在Java编程的世界里,对象序列化与反序列化是实现数据持久化和网络传输的关键技术。本文将深入探讨Java序列化的原理、应用场景以及如何通过代码示例实现对象的序列化与反序列化过程。从基础概念到实践操作,我们将一步步揭示这一技术的魅力所在。
|
1月前
|
JSON 缓存 NoSQL
Redis 在线查看序列化对象技术详解
Redis 在线查看序列化对象技术详解
37 2
下一篇
无影云桌面