I am trying out basic example of using Redis as Cache provider and trying to customise it to use GenericJackson2JsonRedisSerializer instead of default JdkSerializationRedisSerializer.
When I call the same method which is cached 2nd time then it is throwing error with following stacktrace:
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.example.rediscachedemo.BookmarkDTO (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.example.rediscachedemo.BookmarkDTO is in unnamed module of loader 'app')
at com.example.rediscachedemo.BookmarkService$$EnhancerBySpringCGLIB$$d8594d86.getBookmarkById(<generated>) ~[classes/:na]
at com.example.rediscachedemo.BookmarkController.getBookmarkById(BookmarkController.java:19) ~[classes/:na]
You can reproduce the issue using this repository https://github.com/sivaprasadreddy/spring-boot-redis-cache-demo
With default JdkSerializationRedisSerializer it is working fine.
I also saw few issues opening with similar error but they are happening because of spring-boot-devtools which I am not using.
Is it a bug or am I missing some configuration?
Comment From: snicoll
@sivaprasadreddy thanks for the sample but considering that you are defining the CacheManager bean yourself, I don't think this should have been reported against Spring Boot.
Spring Data's GenericJackson2JsonRedisSerializer requires to serialize the type of the object. This is done automatically if you create the serializer with the String-based constructor. If you provide your own mapper, then that feature must be enabled.
I believe the documentation of Spring Data Redis could be improved, I've created https://github.com/spring-projects/spring-data-redis/issues/2140
Comment From: sivaprasadreddy
Just in case someone came across the same issue, it is solved by registering the RedisCacheManager bean as follows:
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
RedisSerializer<Object> serializer = new GenericJackson2JsonRedisSerializer(objectMapper);
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
.entryTtl(Duration.ofMinutes(1));
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultRedisCacheConfiguration())
.build();
}
Comment From: paynezhuang
Just in case someone came across the same issue, it is solved by registering the
RedisCacheManagerbean as follows:``` @Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN); objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); //objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
RedisSerializer<Object> serializer = new GenericJackson2JsonRedisSerializer(objectMapper); RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)) .entryTtl(Duration.ofMinutes(1)); return RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(defaultRedisCacheConfiguration()) .build();} ```
I used your configuration,When saving in redis, you can see the @class information, but when retrieve the value again, it prompts:
Could not read JSON:Unexpected token (START_OBJECT), expected VALUE_STRING: need String, Number of Boolean value that contains type id (for subtype of java.lang.Object)
Complete configuration
@Bean
public <T> RedisTemplate<String, T> redisTemplate(@Autowired LettuceConnectionFactory lettuceConnectionFactory) {
RedisTemplate<String, T> template = new RedisTemplate<>();
template.setConnectionFactory(lettuceConnectionFactory);
StringRedisSerializer keySerializer = new StringRedisSerializer();
RedisSerializer<Object> valueSerializer = RedisSerializer.json();
template.setKeySerializer(keySerializer);
template.setValueSerializer(valueSerializer);
template.setHashKeySerializer(keySerializer);
template.setHashValueSerializer(valueSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
RedisSerializer<Object> serializer = new GenericJackson2JsonRedisSerializer(objectMapper);
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
.entryTtl(Duration.ofMinutes(1));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}