问题
<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
): 表示一个未知类型,它是T
或T
的子类。这种通配符用于读取数据时,确保数据的类型至少是T
。例如,List<? extends Number>
表示一个可以包含Number
或其子类(如Integer
、Double
)的列表。 - 下界通配符 (
? super T
): 表示一个未知类型,它是T
或T
的超类。这种通配符用于写入数据时,确保数据的类型不低于T
。例如,List<? super Integer>
表示一个可以包含Integer
或其超类(如Number
、Object
)的列表。
- 上界通配符 (
2、Object
Object
是 Java 中所有类的根类。它可以用于表示任何类型的对象。使用 Object
类型可以接收任意类型的数据,但它会丧失泛型的类型安全性和特定的方法。通常,当你使用 Object
类型时,你可能需要在运行时进行类型检查和强制类型转换。
主要区别
- 类型安全: 使用通配符可以在一定程度上保持类型安全,而
Object
类型则不提供类型安全,可能需要额外的类型检查和转换。 - 灵活性: 通配符提供了更灵活的类型操作。例如,
List<? extends Number>
可以接受List<Integer>
、List<Double>
等,而List<Object>
则可以接受任何类型,但你失去了更多的类型信息。 - 用途: 通配符主要用于处理泛型集合和方法参数时保持通用性,而
Object
用于接受和处理任意类型的对象,通常在你不关心对象具体类型时使用。
总结
? extends Number>可以接受
List、
List等,而
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