首页 > 数据库 >使用spring-boot集成redis中使用redisTemplate配置泛型导致的问题

使用spring-boot集成redis中使用redisTemplate配置泛型导致的问题

时间:2024-08-13 14:56:03浏览次数:16  
标签:spring boot redis javaTimeModule 类型 new 序列化 class redisTemplate

问题

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

遇到一个问题,使用redis中的List保存部门组装路径后相关数据,方便查询,功能代码为:

@Service
@Slf4j
@RequiredArgsConstructor
public class OACompanyServiceImpl extends ServiceImpl<OACompanyMapper, OACompanyEntity> implements OACompanyService {

    private final OACompanyMapper oaCompanyMapper;
	// 设置redis的泛型
    private final RedisTemplate<String, OAOrgChangeVO> redisTemplate;

    /**
     * 公司名单
     */
    private final static String OA_COMPANY_LIST = "OA_COMPANY_LIST";

    @Override
    public List<OAOrgChangeVO> getChangeCompanyList() {
		// 查看是否存在这个缓存数据
        Boolean hasKey = redisTemplate.hasKey(OA_COMPANY_LIST);
        // 获取操作类
        BoundListOperations<String, OAOrgChangeVO> listOps = redisTemplate.boundListOps(OA_COMPANY_LIST);
        if (Boolean.FALSE.equals(hasKey)) {
        	// 查询对应的部门数据
            MPJLambdaWrapper<OACompanyEntity> queryWrapper = new MPJLambdaWrapper<OACompanyEntity>();
            List<OAOrgChangeVO> vos = oaCompanyMapper.selectJoinList(OAOrgChangeVO.class, queryWrapper);
            if (!vos.isEmpty()){
                OAOrgChangeVO[] values = vos.toArray(new OAOrgChangeVO[]{});
                listOps.leftPushAll(values);
                listOps.expire(12, TimeUnit.HOURS);
            }
            return vos;
        }
        return listOps.range(0, -1);
    }
}

编译通过,但是springboot项目启动结果抛出以下异常在这里插入图片描述

问题排查(配置)

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        RedisSerializer<Object> jackson2serializer = jackson2serializer();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
		// 设置的连接工厂
        redisTemplate.setConnectionFactory(connectionFactory);
        // 设置key和value的序列化方式
        redisTemplate.setKeySerializer(stringRedisSerializer); // 设置key的序列化器为StringRedisSerializer
        redisTemplate.setValueSerializer(jackson2serializer); // 设置value的序列化器为自定义的序列化方式
        redisTemplate.setHashKeySerializer(stringRedisSerializer); // 设置hash key的序列化器为jackson2serializer
        redisTemplate.setHashValueSerializer(jackson2serializer); // 设置hash 自定义的序列化方式
        redisTemplate.afterPropertiesSet(); // 初始化RedisTemplate
        return redisTemplate; // 返回配置好的RedisTemplate
    }
    @Bean
    public RedisSerializer<Object> jackson2serializer() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //必须设置,否则无法将JSON转化为对象,会转化成Map类型
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);

        // 自定义ObjectMapper的时间处理模块
        JavaTimeModule javaTimeModule = new JavaTimeModule();

        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));

        objectMapper.registerModule(javaTimeModule);

        // 禁用将日期序列化为时间戳的行为
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        //创建JSON序列化器
        return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
    }

第一步想到配置是因为源码中 设置的lettuce连接工厂,类似于连接MySQL的sessionFactory

!
我的配置代码中设置连接工厂的地方为我注册的public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) bean;

打上断点,看看效果,进入到源码中,发现设置的setValueSerializer中的javaType为Object类型,但是其泛型使用的为?

源码:在这里插入图片描述

我使用的:在这里插入图片描述

解决方式

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, ?> redisTemplate = new RedisTemplate<>();
        RedisSerializer<?> jackson2serializer = jackson2serializer();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        redisTemplate.setConnectionFactory(connectionFactory);
        // 设置key和value的序列化方式
        redisTemplate.setKeySerializer(stringRedisSerializer); // 设置key的序列化器为StringRedisSerializer
        redisTemplate.setValueSerializer(jackson2serializer); // 设置value的序列化器为自定义的序列化方式
        redisTemplate.setHashKeySerializer(stringRedisSerializer); // 设置hash key的序列化器为jackson2serializer
        redisTemplate.setHashValueSerializer(jackson2serializer); // 设置hash 自定义的序列化方式
        redisTemplate.afterPropertiesSet(); // 初始化RedisTemplate
        return redisTemplate; // 返回配置好的RedisTemplate
    }
    @Bean
    public RedisSerializer<?> jackson2serializer() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //必须设置,否则无法将JSON转化为对象,会转化成Map类型
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);

        // 自定义ObjectMapper的时间处理模块
        JavaTimeModule javaTimeModule = new JavaTimeModule();

        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));

        objectMapper.registerModule(javaTimeModule);

        // 禁用将日期序列化为时间戳的行为
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        //创建JSON序列化器
        return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
    }


}

出错原因

​ 根本原因需要弄清楚 ?(通配符)和使用Object类的区别

1、通配符?)在 Java 泛型中用于表示未知的类型。通配符的主要用途是在处理泛型集合时,使代码更加灵活和通用。通配符可以分为三种类型:

  • 无界通配符 (?): 表示任意类型。它可以用于你不知道具体类型的情况。例如,List<?> 表示一个可以包含任何类型的 List。它允许你读取 List 中的元素,但不能向其中添加元素(除了 null)。
  • 有界通配符 (? extends T? super T):
    • 上界通配符 (? extends T): 表示一个未知类型,它是 TT 的子类。这种通配符用于读取数据时,确保数据的类型至少是 T。例如,List<? extends Number> 表示一个可以包含 Number 或其子类(如 IntegerDouble)的列表。
    • 下界通配符 (? super T): 表示一个未知类型,它是 TT 的超类。这种通配符用于写入数据时,确保数据的类型不低于 T。例如,List<? super Integer> 表示一个可以包含 Integer 或其超类(如 NumberObject)的列表。

2、Object

Object 是 Java 中所有类的根类。它可以用于表示任何类型的对象。使用 Object 类型可以接收任意类型的数据,但它会丧失泛型的类型安全性和特定的方法。通常,当你使用 Object 类型时,你可能需要在运行时进行类型检查和强制类型转换。

主要区别

  • 类型安全: 使用通配符可以在一定程度上保持类型安全,而 Object 类型则不提供类型安全,可能需要额外的类型检查和转换。
  • 灵活性: 通配符提供了更灵活的类型操作。例如,List<? extends Number> 可以接受 List<Integer>List<Double> 等,而 List<Object> 则可以接受任何类型,但你失去了更多的类型信息。
  • 用途: 通配符主要用于处理泛型集合和方法参数时保持通用性,而 Object 用于接受和处理任意类型的对象,通常在你不关心对象具体类型时使用。

总结

? extends Number>可以接受ListList等,而List` 则可以接受任何类型,但你失去了更多的类型信息。

  • 用途: 通配符主要用于处理泛型集合和方法参数时保持通用性,而 Object 用于接受和处理任意类型的对象,通常在你不关心对象具体类型时使用。

总结

​ 在使用Redisconfig的时候,需要考虑是否存在?和Object的转化,上述代码中使用到了jackson2serializer,?和 Object的转化导致注入类型失败,最终导致设置了泛型为OAOrgChangeVO后,出现Object和OAOrgChangeVO的类型匹配的问题,最终不存在类型参数而异常

标签:spring,boot,redis,javaTimeModule,类型,new,序列化,class,redisTemplate
From: https://blog.csdn.net/karlif/article/details/141162207

相关文章

  • 【网站项目】SpringBoot697学生信息管理系统
    ......
  • 【网站项目】SpringBoot698图书管理系统
    ......
  • 【网站项目】SpringBoot697学生信息管理系统
    ......
  • 【网站项目】SpringBoot698图书管理系统
    ......
  • 【2025毕设热门选题】《基于SpringBoot+Vue的民宿管理系统》功能规划和开题报告
    开题报告:《基于SpringBoot+Vue的民宿管理系统》一、选题背景随着互联网技术的飞速发展和人们旅游消费观念的转变,民宿行业近年来呈现出蓬勃发展的态势。传统民宿管理方式存在效率低下、信息不透明、用户体验差等问题,已难以满足市场需求。因此,开发一款高效、便捷、用户友好......
  • 如何处理pbootcms网站被黑被挂马 删除生成无数的灰产链接
    最近pbootcms被疯狂的针对,使用pbootcms系统的企业网站很多都遭到了会产的入侵,植入了很多会产链接。目前已知的是pbootcms3.2.5以下版本存在if标签漏洞,官方已于3.2.5版本进行了修复。网站被黑的小伙伴们,可以对应检查一下自己使用的pbootcms的版本。也有一部分使用最新pbootcms......
  • pbootcms网站是使用sqlite数据库好还是使用mysql数据库好?
    众多周知pbootcms程序支持sqlite数据库和mysql数据库,目前默认常用最多的是sqlite数据库,有需要转成mysql数据库的可以联系我们。pbootcms数据库sqlite无缝转换mysql数据库 本人从接触pbootcms开始一直都是使用mysql数据库,很少出现被黑和各种不明原因报错。建议有条件的朋友尽量......
  • Spring Boot 缓存优化攻略
    1.确定待缓存的对象首先,我们需要明确哪些对象最适合缓存。一般而言,那些代价高昂且耗时的操作的结果需要优先考虑,例如数据库查询、网络服务调用或复杂计算的结果。然而,定义一些理想缓存候选对象的通用特征将更重要。这些特征有助于我们在应用程序中识别适合缓存的对象:频繁访......
  • 个人技能总结-redis部分
    Redis部分技能总结架构总结Redis目前在用分为主从模式,sentinel主从模式集群Redis-cluster集群模式3种模式。redis-cluster是多master模式由多个maste共同维护16384个slotredis-sentinel是单master模式主从模式由sentinel控制主从切换和健康检查。种方式的优缺点Sen......
  • redis 计算key的数量
    在Redis中,可以使用DBSIZE命令来计算键的总数。这个命令返回当前数据库的键的数目。如果你想计算特定模式的键的数目,可以使用SCAN命令结合计数逻辑。下面是一个使用SCAN和MATCH选项的例子,用于计算匹配特定模式的键的数目:  redis-cli--scan--pattern'your_pattern*'|wc......