문제 상황은 다음과 같다. 기존 redis template을 사용 중이었기에
간단하게 캐싱을 달 수 있을 줄 알았다.
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
ObjectMapper objectMapper = new ObjectMapper();
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(24)) // 캐시 유효 기간 설정
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer(objectMapper)));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(cacheConfig)
.build();
}
이렇게 설정하고 @cacheable을 통해서 캐싱을 할려고 했더니
ClassCastException 이 발생했다.
원인은 다음과 같다.
Spring에서 Redis를 직렬화/역직렬화 할때, 여러가지 serializer가 존재하는데,
문제는 Jackson2JsonRedisSerializer 는 클래스 정보가 직렬화 할 때 포함되지 않는다는 것이다.
이게 어떤 문제가 발생하냐면, 캐싱한 값을 다시 가져와서 역직렬화 하는 과정에서 내부 값으로 objectMapper를 사용하여 class로 변환을 시킨다.
근데 어떤 class인지에 대한 명시적 값이 없기 때문에 그냥 LinkedHashedMap 형태로 key:value 로 변환을 해버린다.
물론 명시적으로 변환할 class를 지정하여 해결 할 수 있다. 하지만, 캐싱하는 dto 타입이 한 두 가지가 아니라는 점에서 사실상 사용이 불가능하다고 생각했다.
그래서 GenericJackson2JsonRedisSerializer 를 사용하기로 했다. 해당 시리얼라이저는 직렬화 할 때 클래스 정보가 포함된다.
"{@class:<className>, ...}"
이런 식으로 값이 들어가게 되어 정상적으로 캐싱을 할 수 있게 되었다.