【小家Spring】Redis序列化、RedisTemplate序列化方式大解读,介绍Genericjackson2jsonredisserializer序列化器的坑(中)

简介: 【小家Spring】Redis序列化、RedisTemplate序列化方式大解读,介绍Genericjackson2jsonredisserializer序列化器的坑(中)

Jackson2JsonRedisSerializer和GenericJackson2JsonRedisSerializer的异同


Jackson2JsonRedisSerializer:为我们提供了两个构造方法,一个需要传入序列化对象Class,一个需要传入对象的JavaType:


  public Jackson2JsonRedisSerializer(Class<T> type) {
    this.javaType = getJavaType(type);
  }
  public Jackson2JsonRedisSerializer(JavaType javaType) {
    this.javaType = javaType;
  }


但因为redisTemplate我们都是单例的,所以这样设置显然是非常不可取的行为。虽然它有好处~~~~~~~~~~


这种序列化方式的好处:他能实现不同的Project之间数据互通(因为没有@class信息,所以只要字段名相同即可),因为其实就是Json的返序列化,只要你指定了类型,就能反序列化成功(因为它和包名无关)


使用这种Json序列化方式果然是可以成功的在不同project中进行序列化和反序列化的。但是,但是,但是:在实际的使用中,我们希望职责单一和高内聚的,所以并不希望我们存在的对象,其它服务可以直接访问,那样就非常不好控制了,因此此种方式也不建议使用~


GenericJackson2JsonRedisSerializer:这种序列化方式不用自己手动指定对象的Class。所以其实我们就可以使用一个全局通用的序列化方式了。使用起来和JdkSerializationRedisSerializer基本一样。


同样的JdkSerializationRedisSerializer不能序列化和反序列化不同包路径对象的毛病它也有。因为它序列化之后的内容,是存储了对象的class信息的:


image.png



========> Jackson2JsonRedisSerializer的坑:


存储普通对象的时候没有问题,但是当我们存储带泛型的List的时候,反序化就会报错了:

    @Test
    public void contextLoads() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(List.class));
        ValueOperations<String, List<Person>> valueOperations = redisTemplate.opsForValue();
        valueOperations.set("aaa", Arrays.asList(new Person("fsx", 24), new Person("fff", 30)));
        List<Person> p = valueOperations.get("aaa");
        System.out.println(p); //[{name=fsx, age=24}, {name=fff, age=30}]
        List<Person> aaa = (List<Person>) redisTemplate.opsForValue().get("aaa");
        System.out.println(aaa); //[{name=fsx, age=24}, {name=fff, age=30}]
    }



结论:网上很多帖子都说这样会出问题,但我实验过后发现不会有问题。时间有限,我这个是基于Spring Boot2.1进行测试的,若你们测试的版本有问题,欢迎告知我,我再做进一步的验证,多谢。


========> GenericJackson2JsonRedisSerializer的坑:

    @Test
    public void contextLoads() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        ValueOperations<String, Long> valueOperations = redisTemplate.opsForValue();
        valueOperations.set("aaa", 1L);
        //Long p = valueOperations.get("aaa"); //转换异常 java.lang.Integer cannot be cast to java.lang.Long
        Object p = valueOperations.get("aaa");
        System.out.println(p);
    }


**坑1:**泛型里明明返回的就是Long类型,但你用Long接,就直接抛出转换异常了


image.png


从上图中我们可以清晰的看见,get出来返回的真实类型竟然是Integer类型,所以强转肯定报错啊


再看一例:set类型


    @Test
    public void contextLoads() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        SetOperations<String, Long> setOperations = redisTemplate.opsForSet();
        setOperations.add("bbb", 1L);
        setOperations.add("bbb", 2L);
        Set<Long> p = setOperations.members("bbb");
        System.out.println(p);
    }


image.png


我们发现,里面装的竟然,竟然是Integer类型。这种Java泛型的bug我们在之前的博文里有讲述过,特别坑。这个时候这个变量就是个地雷,只要一碰,就报错


另外,就算你获取的并不是List类型,而是一个值,也必须要转换一下,否则类型转换异常。像下面这么操作才是安全的:


            Object teaIdObj = setOperLong.pop(teaCategoryKey);
            if (teaIdObj != null) {
                log.info("从redis老师仓库{}里拿到了一个老师{}", teaCategoryKey, teaIdObj);
                teacherIds.add(Long.parseLong(teaIdObj.toString()));
            }

类型转换异常原因分析


因为GenericJackson2JsonRedisSerializer这种序列化方式实在是太通用了,所以我还是希望找出原因,解决这个问题的。因此我就跟踪源码,看看到底是哪里出了问题:

执行setOperations.members("bbb")这句最终都到了RedisTemplate的execute方法:

public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {...}


方法体的这一行,解析了返回的value值:


T result = action.doInRedis(connToExpose);


tips:Spring Boot1.x此处connToExpose使用的是jedis的,而Boot2.x使用的是Lettuce的了。但是对调用者是透明的,可谓非常友好


继续跟踪发现,最终会调用我们配置好的序列化器进行序列化:


  V deserializeValue(byte[] value) {
    if (valueSerializer() == null) {
      return (V) value;
    }
    return (V) valueSerializer().deserialize(value);
  }


因此啥都不说了,到GenericJackson2JsonRedisSerializer去看看它的deserialize方法吧,就在这一句话:


// 调用了jackson的ObjectMapper方法进行返序列化  但是type为Object.class
return mapper.readValue(source, type);


image.png


为何我的泛型类型丢失了呢?向上追溯一步,我们发现:


  static <T extends Collection<?>> T deserializeValues(@Nullable Collection<byte[]> rawValues, Class<T> type,
      @Nullable RedisSerializer<?> redisSerializer) {
    // connection in pipeline/multi mode
    if (rawValues == null) {
      return (T) CollectionFactory.createCollection(type, 0);
    }
    Collection<Object> values = (List.class.isAssignableFrom(type) ? new ArrayList<>(rawValues.size())
        : new LinkedHashSet<>(rawValues.size()));
    for (byte[] bs : rawValues) {
      values.add(redisSerializer.deserialize(bs));
    }
    return (T) values;
  }


我们的类型全部变成了Collection里面的Object类型,我们的泛型就这样丢失了

。所以在序列化的时候,只要遇到数字(或者泛型),自然就是当作Integer来处理了,因此就出现了我们看到的诡异现象。


因为GenericJackson2JsonRedisSerializer本来处理序列化的都是与类型无关的,所以都转换为Object进行处理。因此出现此种现象也是在情理之中的。

相关文章
|
3月前
|
存储 NoSQL Java
配置RedisTemplate序列化机制
通过上述步骤,你可以灵活配置RedisTemplate的序列化机制,根据应用需求选择合适的序列化器,从而确保数据在Redis中的存储和读取效率最优化。配置合适的序列化机制对于性能和存储效率至关重要,而且这样可以确保数据在存储和传输过程中的结构清晰和一致性。
270 11
|
8月前
|
NoSQL 安全 Java
深入理解 RedisConnectionFactory:Spring Data Redis 的核心组件
在 Spring Data Redis 中,`RedisConnectionFactory` 是核心组件,负责创建和管理与 Redis 的连接。它支持单机、集群及哨兵等多种模式,为上层组件(如 `RedisTemplate`)提供连接抽象。Spring 提供了 Lettuce 和 Jedis 两种主要实现,其中 Lettuce 因其线程安全和高性能特性被广泛推荐。通过手动配置或 Spring Boot 自动化配置,开发者可轻松集成 Redis,提升应用性能与扩展性。本文深入解析其作用、实现方式及常见问题解决方法,助你高效使用 Redis。
911 4
|
3月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
280 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
4月前
|
存储 NoSQL Java
RedisTemplate序列化问题排查与优化建议。
最后,对于序列化问题的深入排查与优化,建议编写具有代表性的单元测试,以验证RedisTemplate配置的正确性和效能。此外,可以在非生产环境中测试不同的序列化策略,找到最适合当前应用场景的序列化方式。通过不断迭代和优化,达到持续提高数据存取能力的目标。
235 13
|
9月前
|
NoSQL Java 关系型数据库
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
本文介绍在 Spring Boot 中集成 Redis 的方法。Redis 是一种支持多种数据结构的非关系型数据库(NoSQL),具备高并发、高性能和灵活扩展的特点,适用于缓存、实时数据分析等场景。其数据以键值对形式存储,支持字符串、哈希、列表、集合等类型。通过将 Redis 与 Mysql 集群结合使用,可实现数据同步,提升系统稳定性。例如,在网站架构中优先从 Redis 获取数据,故障时回退至 Mysql,确保服务不中断。
380 0
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
|
5月前
|
NoSQL Java Redis
Redis基本数据类型及Spring Data Redis应用
Redis 是开源高性能键值对数据库,支持 String、Hash、List、Set、Sorted Set 等数据结构,适用于缓存、消息队列、排行榜等场景。具备高性能、原子操作及丰富功能,是分布式系统核心组件。
604 2
|
7月前
|
消息中间件 缓存 NoSQL
基于Spring Data Redis与RabbitMQ实现字符串缓存和计数功能(数据同步)
总的来说,借助Spring Data Redis和RabbitMQ,我们可以轻松实现字符串缓存和计数的功能。而关键的部分不过是一些"厨房的套路",一旦你掌握了这些套路,那么你就像厨师一样可以准备出一道道饕餮美食了。通过这种方式促进数据处理效率无疑将大大提高我们的生产力。
263 32
|
9月前
|
NoSQL Java API
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Spring Boot 集成 Redis
本文介绍了在Spring Boot中集成Redis的方法,包括依赖导入、Redis配置及常用API的使用。通过导入`spring-boot-starter-data-redis`依赖和配置`application.yml`文件,可轻松实现Redis集成。文中详细讲解了StringRedisTemplate的使用,适用于字符串操作,并结合FastJSON将实体类转换为JSON存储。还展示了Redis的string、hash和list类型的操作示例。最后总结了Redis在缓存和高并发场景中的应用价值,并提供课程源代码下载链接。
2183 0
|
9月前
|
NoSQL Java Redis
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 安装
本教程介绍在 VMware 虚拟机(CentOS 7)或阿里云服务器中安装 Redis 的过程,包括安装 gcc 编译环境、下载 Redis(官网或 wget)、解压安装、修改配置文件(如 bind、daemonize、requirepass 等设置)、启动 Redis 服务及测试客户端连接。通过 set 和 get 命令验证安装是否成功。适用于初学者快速上手 Redis 部署。
292 0
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
760 5