首页 > 解决方案 > 如何使用 Spring Boot 2 CacheManager 在 Redis 中存储非类型化 JSON

问题描述

我在 Spring Boot 2 应用程序中使用 Redis 作为缓存存储。我@Cacheable在某些方法中使用了注释,并且我想将 Redis 中的数据作为非类型化 JSON 存储。使用我当前的配置,保存数据可以正常工作,但读取它会生成ClassCastException.

所有解决方案、答案、示例和教程都使用 JacksonObjectMapper来配置RedisTemplateRedisCacheConfiguration向 JSON 添加默认类型属性。这里的问题是这个缓存将由不同语言/技术的不同应用程序共享,我不能强迫其他应用程序像 Spring Boot 那样工作。

这是我现在拥有的:

配置

@Bean
CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    return RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory)
            .cacheDefaults(cacheConfiguration())
            .build();
}

private RedisCacheConfiguration cacheConfiguration() {
    return RedisCacheConfiguration.defaultCacheConfig()
            .disableCachingNullValues()
            .serializeKeysWith(SerializationPair.fromSerializer(RedisSerializer.string()))
            .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(redisMapper())));
}

private ObjectMapper redisMapper() {
    return new ObjectMapper()
                //.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY)
                .setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
}

服务

@Cacheable(key = "'persons::' + #id")
public Person getPerson(Long id) {
    // return person from DB
}

当前配置的结果:

{
  "name": "John",
  "lastName": "Doe",
  "age": 31
}

当 Spring 尝试使用反序列化器从缓存中读取内容时,它找不到"@class"具有类型信息的属性,因此它返回一个LinkedHashMap. 之后,CacheInterceptor尝试将 a 转换LinkedHashMap为 aPerson并且发生这种ClassCastException情况。

Request processing failed; nested exception is java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.test.dto.Person

在这一点上,如果我需要为要存储的每种类型编写一个序列化程序,或者我可以为所有人创建一个自定义序列化程序,我就可以了。到目前为止,我的研究一直没有成功。

谢谢你的时间。

标签: jsonspringspring-bootcachingredis

解决方案


我现在也在和这个打架。甚至添加打字也无济于事,因为如果您@Cacheable在返回“原始”类型的方法上使用Long(即使在集合中),您也会Integer回来,因为杰克逊使用智能数字映射,如果数字适合在那​​里,则返回 32 位整数。

是的,您现在可以使用 flag USE_LONG_FOR_INTS,但这意味着它总是返回Long,而不是首先放入缓存中的类型。

因此,如果您想将漂亮的 JSON 保存到 redis,则不能使用@Cacheable,因为您可能Class cast exception会经常遇到这种情况。

但是您可以手动使用缓存管理器,将 JSON 保存为纯文本并自己反序列化它,自己为对象映射器提供所需的返回类(不幸的是,注释不能自动为您做)。

示例代码(不完美且不处理来自对象映射器的异常):

@Autowired
private CacheManager cacheManager;

@Autowired
private ObjectMapper objectMapper;

public Person getPerson(Long id) {
    Cache persons = cacheManager.getCache("persons");
    if (persons == null) return // cache not exist, should not happen, return person from DB

    return Optional.ofNullable(persons.get(id))
            .map(Cache.ValueWrapper::get)
            .map(o -> objectMapper.readerFor(Person.class).readValue(o))
            .orElseGet(() -> {
                Person p = // get person from DB
                persons.put(id, objectMapper.writeValueAsString(p));
                return p;
            });
}

推荐阅读