redis에 객체(dto)를 저장할 때 serializer를 통해 직렬화해주어야 한다.
이 때, 선택할 수 있는 여러가지 직렬화 방법이 존재한다.
Class Type을 지정해야 하며, redis에 객체를 저장할 때 class 값 대신 Classy Type 값을 JSON 형태로 저장한다.
pacakge 등의 정보 일치를 고려할 필요가 없다는 장점이 있다.
하지만, class type을 지정해야 하기 때문에 특정 클래스에 종속적
이며, redisTemplate을 여러 쓰레드에서 접근하게 될 때 serializer 타입의 문제가 발생하는 경우가 발생한다.
StringRedisSerializer는 String 값을 그대로 저장한다.
JSON 형태로 직접 encoding, decoding을 해줘야한다는 단점이 있지만 위의 두 개의 serializer에서 발생할 수 있는 문제가 발생하지 않는다.
class 타입을 지정할 필요가 없다.
쓰레드간의 문제가 발생하지 않는다.
객체의 클래스 지정 없이
모든 Class Type을 JSON 형태로 저장
할 수 있는 Serializer이다
Class Type에 상관 없이 모든 객체를 직렬화해준다는 장점을 가지고 있다.
하지만, 단점으로는 Object의 class 및 package까지 전부 함께 저장하게 되어 다른 프로젝트에서 redis에 저장되어 있는 값을 사용하려면 package까지 일치시켜줘야한다.
따라서 MSA 구조의 프로젝트 같은 경우 문제가 생길 수 있을 것 같다.
나는 여러 객체를 캐싱해야 했기 때문에, 여러 객체를 직렬화/역직렬화 사용할 수 있는
GenericJackson2JsonRedisSerializer
를 사용하기로 했다.
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로 지원이 안되는 것 같다.
jackson-datatype-jsr310
의존성을 추가한다.에러 문구에 나와있는대로, 해당 모듈을 추가해봤지만, 여전히 에러를 뱉었다.
Custom Serializer
사용하기이런 형식인데, 찾아보니 Serializer를 커스텀할 필요가 없고, 더 간편한 방법이 있었다.
Custom ObejctMapper
생성하기Custom ObjectMapper를 생성하고, JavaTimeModule을 등록해준 후 GenericJackson2JsonRedisSerializer의 파라미터로 해당 ObjectMapper를 넘겨주면 된다.
registerModule()
: 추가하고 싶은 모듈을 추가할 수 있다. 여기서는 JavaTimeModule을 추가하여 LocalDateTime 역직렬화를 가능하도록 함.
enableDefaultTyping()
: 직렬화 시 type 정보를 저장할 scope를 지정한다. 여기서는 non-final 클래스들에 대해 타입 정보를 저장할 수 있도록 했다.(GenericJackson2JsonRedisSerializer의 기본 동작 방식)
하지만 얘는 2.10 버전 이후로 Deprecated 됐으며, activateDefaultTyping()
을 사용이 권장되므로 코드를 수정한다.
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이 존재하지 않았던 것이다.
분명 GenericJackson2JsonRedisSerializer
는 class 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을 함께 직렬화/역직렬화 하도록 설정해주어야 한다.
저랑 비슷한 문제를 겪으신 듯 하여 찾아왔습니다 :D 저도 Generic ~ 을 직접 구현했는데, 스프링 3.1.1 부터는 좀 더 세련되게 ObjectMapper 를 변경할 수 있게 되었어요. 참고해보세요!
https://github.com/spring-projects/spring-data-redis/issues/2722