【问题标题】:How to store non-typed JSONs in Redis with Spring Boot 2 CacheManager如何使用 Spring Boot 2 CacheManager 在 Redis 中存储非类型化 JSON
【发布时间】:2019-10-31 09:14:31
【问题描述】:

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

所有解决方案、答案、示例和教程都使用 Jackson 的 ObjectMapper 配置 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 尝试将LinkedHashMap 转换为Person,然后出现ClassCastException

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

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

感谢您的宝贵时间。

【问题讨论】:

    标签: json spring spring-boot caching redis


    【解决方案1】:

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

    是的,您现在可以使用标志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;
                });
    }
    

    【讨论】:

      【解决方案2】:

      老实说,我不确定这是否是您问题的答案,因为它不会像您的示例那样创建如此“干净”和“美丽”的 JSON,但它对我来说可以序列化和反序列化。在此示例中,缓存管理器设置为 TTL,您可以根据需要将其删除。

      @Configuration
      @EnableCaching
      public class RedisCacheConfig {
      
        @Value("${spring.redis.host}")
        private String redisHostName;
      
        @Value("${spring.redis.port}")
        private int redisPort;
      
        @Bean
        public LettuceConnectionFactory redisConnectionFactory() {
          return new LettuceConnectionFactory(new RedisStandaloneConfiguration(redisHostName, redisPort));
        }
      
        @Bean
        public RedisTemplate<Object, Object> redisTemplate() {
          RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
          redisTemplate.setConnectionFactory(redisConnectionFactory());
          return redisTemplate;
        }
      
        @Bean
        @Primary
        public RedisCacheManager redisCacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
          RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
            .entryTtl(Duration.ofMinutes(1))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
      
          redisCacheConfiguration.usePrefix();
      
          return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(lettuceConnectionFactory)
            .cacheDefaults(redisCacheConfiguration).build();
      
        }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-25
        • 1970-01-01
        • 2020-02-16
        • 2017-06-12
        • 2020-05-13
        • 1970-01-01
        • 2015-06-21
        • 2018-12-16
        相关资源
        最近更新 更多