Spring Redis 역직렬화 삽질기 (feat. RedisSerializer)

bagt13·2023년 2월 20일
16

Project

목록 보기
11/19
post-thumbnail

redis에 객체(dto)를 저장할 때 serializer를 통해 직렬화해주어야 한다.
이 때, 선택할 수 있는 여러가지 직렬화 방법이 존재한다.


✅ 1. Jackson2JsonRedisSerializer

Class Type을 지정해야 하며, redis에 객체를 저장할 때 class 값 대신 Classy Type 값을 JSON 형태로 저장한다.

  • pacakge 등의 정보 일치를 고려할 필요가 없다는 장점이 있다.

  • 하지만, class type을 지정해야 하기 때문에 특정 클래스에 종속적이며, redisTemplate을 여러 쓰레드에서 접근하게 될 때 serializer 타입의 문제가 발생하는 경우가 발생한다.


✅ 2. StringRedisSerializer

StringRedisSerializer는 String 값을 그대로 저장한다.

JSON 형태로 직접 encoding, decoding을 해줘야한다는 단점이 있지만 위의 두 개의 serializer에서 발생할 수 있는 문제가 발생하지 않는다.

  • class 타입을 지정할 필요가 없다.

  • 쓰레드간의 문제가 발생하지 않는다.


✅ 3. GenericJackson2JsonRedisSerializer

객체의 클래스 지정 없이 모든 Class Type을 JSON 형태로 저장할 수 있는 Serializer이다

  • Class Type에 상관 없이 모든 객체를 직렬화해준다는 장점을 가지고 있다.

  • 하지만, 단점으로는 Object의 class 및 package까지 전부 함께 저장하게 되어 다른 프로젝트에서 redis에 저장되어 있는 값을 사용하려면 package까지 일치시켜줘야한다.

  • 따라서 MSA 구조의 프로젝트 같은 경우 문제가 생길 수 있을 것 같다.

나는 여러 객체를 캐싱해야 했기 때문에, 여러 객체를 직렬화/역직렬화 사용할 수 있는 GenericJackson2JsonRedisSerializer를 사용하기로 했다.


❌ 에러

❗️ java.time.LocalDateTime not Supported

Resolved [org.springframework.data.redis.serializer.SerializationException:
Could not write JSON: Java 8 date/time type `java.time.LocalDateTime`
not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling

앞서 봤듯이 GenericJackson2JsonRedisSerializer을 사용하면 class type에 상관 없이 직렬화/역직렬화가 가능하다. 하지만 날짜 타입에 대해서는 default로 지원이 안되는 것 같다.


📒 시도한 방법

📘 1. jackson-datatype-jsr310 의존성을 추가한다.

에러 문구에 나와있는대로, 해당 모듈을 추가해봤지만, 여전히 에러를 뱉었다.


📘 2. Custom Serializer 사용하기

이런 형식인데, 찾아보니 Serializer를 커스텀할 필요가 없고, 더 간편한 방법이 있었다.


📘 3. Custom ObejctMapper 생성하기

Custom ObjectMapper를 생성하고, JavaTimeModule을 등록해준 후 GenericJackson2JsonRedisSerializer의 파라미터로 해당 ObjectMapper를 넘겨주면 된다.

  • registerModule() : 추가하고 싶은 모듈을 추가할 수 있다. 여기서는 JavaTimeModule을 추가하여 LocalDateTime 역직렬화를 가능하도록 함.

  • enableDefaultTyping() : 직렬화 시 type 정보를 저장할 scope를 지정한다. 여기서는 non-final 클래스들에 대해 타입 정보를 저장할 수 있도록 했다.(GenericJackson2JsonRedisSerializer의 기본 동작 방식)


하지만 얘는 2.10 버전 이후로 Deprecated 됐으며, activateDefaultTyping()을 사용이 권장되므로 코드를 수정한다.


❗️ LinkedHasmap cannot be cast to class DTO Object

class java.util.LinkedHashMap cannot be cast to class ...web.board.dto.BoardResponseDto
(java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.

이 에러는 위에서 ObjectMapper에 설정한 activateDefaultTyping()이 없을 때 만났던 에러이다. 이 메서드는 직렬화 시 기본적으로 type information을 함께 저장하도록 한다.


📒 문제 원인 찾기

이때 의아했던 점은, Redis에 저장된 데이터에 class type이 존재하지 않았던 것이다.

❓❓❓

분명 GenericJackson2JsonRedisSerializerclass type 정보를 기본적으로 함께 저장한다고 알고 있고, custom ObjectMapper를 사용하기 전에는 class type을 함께 저장했었다.

하지만 custom ObjectMapper를 등록 후 에러를 뱉기 시작했고, 이유는 다음과 같았다.


📒 에러 원인

먼저, GenericJackson2JsonRedisSerializer가 기본적으로 직렬화 시 class type(@class 속성)을 함께 정보하는 건 맞다.

하지만, GenericJackson2JsonRedisSerializer가 custom한 ObjectMapper를 사용할 때는 다른 이야기이다.

ObjectMapper는 기본적으로 직렬화/역직렬화 시 class type 정보를 포함하지 않기 때문에, 직렬화된 데이터에는 type 정보가 존재하지 않는다.

또한 역직렬화 시에도 ObjectMapper가 type 정보를 모른 채 역직렬화를 진행하게 되고, 기본 타입인 LinkedHashMap으로 역직렬화 되어 에러가 나는 것이다.


✅ 해결

따라서, 앞서 본 것처럼 ObjectMapper에 activateDefaultTyping()을 통해 class type을 함께 직렬화/역직렬화 하도록 설정해주어야 한다.

profile
주니어 백엔드 개발자입니다😄

2개의 댓글

comment-user-thumbnail
2023년 10월 4일

저랑 비슷한 문제를 겪으신 듯 하여 찾아왔습니다 :D 저도 Generic ~ 을 직접 구현했는데, 스프링 3.1.1 부터는 좀 더 세련되게 ObjectMapper 를 변경할 수 있게 되었어요. 참고해보세요!

https://github.com/spring-projects/spring-data-redis/issues/2722

1개의 답글