TIL(Today I Learned)

TIL - Spring Boot에서 Redis Cache 사용 시 발생하는 역직렬화 문제

Happy._. 2024. 7. 23. 22:01

게시글 및 리뷰에 대한 인기글 조회를 구현하면서 일정 시간마다 데이터를 Cache해서 보여주기 위해 Redis Cache를 사용했다.

 

초기 RedisConfig 코드는 다음과 같다.

@Configuration
@EnableCaching
class RedisCacheConfig {

    @Bean
    fun redisCacheManager(redisConnectionFactory: RedisConnectionFactory): CacheManager =

        RedisCacheManager
            .builder(redisConnectionFactory)
            .cacheDefaults(
                RedisCacheConfiguration
                    .defaultCacheConfig()
                    .entryTtl(Duration.ofMinutes(1))
                    .disableCachingNullValues()
            )
            .build()
}

 

직렬화에 대한 별도 설정을 하지 않았기 때문에 Redis에 저장되는 데이터는 다음과 같이 저장되어 있지만, 역직렬화 시에는 데이터 값이 첫 줄에 있는 class type 정보를 이용해 역직렬화 하기 때문에 정상적인 형태로 출력된다.

 

직렬화 설정으로 저장되는 데이터도 보기 쉽도록 바꾸기 위해 RedisCacheCofig 코드를 다음과 같이 변경하였다.

@Configuration
@EnableCaching
class RedisCacheConfig {

    @Bean
    fun redisCacheManager(redisConnectionFactory: RedisConnectionFactory): CacheManager =

        RedisCacheManager
            .builder(redisConnectionFactory)
            .cacheDefaults(
                RedisCacheConfiguration
                    .defaultCacheConfig()
                    .entryTtl(Duration.ofMinutes(1))
                    .disableCachingNullValues()
                    .serializeKeysWith(
                        RedisSerializationContext
                            .SerializationPair
                            .fromSerializer(StringRedisSerializer())
                    )
                    .serializeValuesWith(
                        RedisSerializationContext
                            .SerializationPair
                            .fromSerializer(
                                GenericJackson2JsonRedisSerializer()
                            )
                    )
            )
            .build()
}

 

위 코드 그대로 서버를 실행하고 요청을 보내면 다음과 같은 예외가 발생한다.

Jackson 라이브러리가 Java 8의 ZonedDateTime 타입을 지원하지 않기 때문에 발생하는 문제이다.

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
Java 8 date/time type `java.time.ZonedDateTime` not supported by default: 
add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: 
java.util.ArrayList[0]->spartacodingclub.nbcamp.kotlinspring.project.team4ighting.spring4gamer.domain.gamereview.dto.response.GameReviewResponse["createdAt"])

 

위 문제를 해결하기 위해 ObjectMapper를 사용해 JavaTimeModule을 추가하면 되지만, 다음 예외 메시지처럼 또 다른 문제가 발생한다.

Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: object is not an instance of declaring class]

 

이유는 단순히 List형태의 JSON 데이터가 들어가기 때문 class type에 대한 정보가 없다.

(처음 데이터가 저장된 이미지를 보면 class type이 같이 저장되어 있는 것을 볼 수 있다.)

 

GenericJackson2JsonRedisSerializer는 기본적으로 class type 정보를 함께 저장하지만 Custom ObjectMapper를 등록하면 직렬화/역직렬화 시 class type 정보를 포함하지 않기 때문에 문제가 발생한 것이다.

 

RedisCacheCofig 코드를 다시 한 번 수정해서 다음과 같이 작성한다.

@Configuration
@EnableCaching
class RedisCacheConfig {

    @Bean
    fun redisCacheManager(redisConnectionFactory: RedisConnectionFactory): CacheManager =

        RedisCacheManager
            .builder(redisConnectionFactory)
            .cacheDefaults(
                RedisCacheConfiguration
                    .defaultCacheConfig()
                    .entryTtl(Duration.ofMinutes(1))
                    .disableCachingNullValues()
                    .serializeKeysWith(
                        RedisSerializationContext
                            .SerializationPair
                            .fromSerializer(StringRedisSerializer())
                    )
                    .serializeValuesWith(
                        RedisSerializationContext
                            .SerializationPair
                            .fromSerializer(
                                GenericJackson2JsonRedisSerializer(
                                    jacksonObjectMapper()
                                        .registerModules(JavaTimeModule())
                                        .activateDefaultTyping(
                                            BasicPolymorphicTypeValidator
                                                .builder()
                                                .allowIfSubType(Object::class.java)
                                                .build(),
                                            ObjectMapper.DefaultTyping.NON_FINAL
                                        )
                                )
                            )
                    )
            )
            .build()
}

 

  • activateDefaultTyping
    • 기본 타이핑 활성화
    • 직렬화된 JSON에 클래스 정보를 포함시켜, 역직렬화 시에 원래의 객체 타입을 복원할 수 있게 하는 기능
  • BasicPolymorphicTypeValidator
  • Jackson 라이브러리에서 제공하는 클래스
  • 다형성을 가진 객체를 직렬화하거나 역직렬화할 때 사용
    • 역직렬화할 실제 하위 유형이 지정된 기준에 따라 유효한지 확인하는 데 사용

 

서버를 실행 후 요청을 보내면 다음과 같이 보기 좋게 class type과 데이터를 확인 할 수 있다.