[이룸] Redis 저장을 위한 직렬화 / 역직렬화
채팅 내역 저장 코드를 살펴보던 중, 다음과 같은 부분을 발견하였다.
@Getter
@Setter
public class ChatMessage {
private String messageId;
private MessageType type;
private String message;
private String sender;
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime time;
private String memberId;
private String challengeId;
private String profileImageUrl;
public enum MessageType {
CHAT,
JOIN,
LEAVE,
DELETE
}
}
이룸의 발표 내용에는 이런 내용도 있었다.
당시에는 프로젝트 완성과 발표에 급급해 이해하지 못했던 부분인데, 오늘 다시 살펴보려 한다.
1. Serialize와 Deserialize
먼저, JsonSerialize 를 이해하려면 Serialize(직렬화)와 Deserialize(역직렬화)에 대해 알아야 한다. 직렬화는 객체나 데이터의 저장과 전송을 쉽게 하기 위하여 '바이트 코드'로 변환하는 것을 의미한다. 역직렬화는 '바이트 코드'를 다시 객체나 데이터로 만드는 것을 말한다.
이 설명에서 '바이트 코드' -> 'Json 형식'으로 바꾸면 Json 직렬화 / Json 역직렬화다.
2. LocalDateTime에 @JsonSerialize 어노테이션을 붙여야 하는 이유
보통 Redis는 문자열을 저장한다. 하지만 LocalDateTime은 문자열이 아니다. 따라서 LocalDateTime 타입의 객체를 Redis에 저장하기 위해서는 Json 문자열로 만들어 주어야 한다.
@JsonSerialize의 특징은 json으로 변환을 할 때 어떤 serializer를 쓸 것인지 지정할 수 있다는 것이다. 여기서는 LocalDateTimeSerializer.class를 사용했다. 이 serializer를 사용하면 자동으로 다음의 형식에 맞추어 Json 문자열로 변환해 준다.
"yyyy-MM-dd'T'HH:mm:ss"
3. @JsonSerialize를 사용하는 방법
@JsonSerialize는 Jackson 라이브러리에 사용할 수 있으므로 build.gradle에 다음과 같은 의존성을 추가한다.
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.3'
여기서 주목할 것은 "jsr310". LocalDateTimeSerializer.class를 들어가보면 JSR310FormattedSerializerBase 클래스를 extend 하고 있다.
Oracle에서는 자바의 개선과 더 나은 표준화를 위한 조직인 JCP(Java Community Process)를 운영한다. 이곳에서는 JSR(Java Specification Request)라는 자바의 특정 기능을 수행하기 위한 명세를 관리하는데, Java8에서 이전에 사용되었던 Date, Calendar 클래스의 한계를 극복하고자 새로운 날짜 및 시간 명세인 JSR310을 도입하였다. 이 명세에 따라 작성된 API가 Java 8에 도입된 java.time 패키지다. 이 패키지 하위에 있는 클래스들이 우리에게 익숙한 LocalDate, LocalTime, LocalDateTime 클래스이다.
따라서 위에 추가한 의존성은 jackson에서 새로 도입된 날짜, 시간 객체를 다룰 수 있도록 도와주는 모듈이라고 할 수 있다.
이제 RedisConfig 클래스로 이동해서 ObjectMapper 클래스 객체를 생성하고 JavaTimeModule을 추가해 준다.
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
// Key와 Value의 Serializer 설정
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
return redisTemplate;
}
ObjectMapper는 Jackson 라이브러리에서 JSON을 처리하기 위해 사용하는 클래스이다. JavaTimeModule을 등록하면 날짜와 시간(JSR310 관련된 API들)을 잘 처리하여 Json 직렬화 / 역직렬화해 준다.
따라서 ObjectMapper를 사용하면 아래의 세 어노테이션을 모두 쓸 필요가 없다...!
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime time;
4. @JsonFormat
이 어노테이션은 Json화할 때의 포맷을 지정할 수 있다. 여기서 두 가지 요청이 들어 있다. 1) String으로 바꿀게요. 2) 패턴은 "yyyy-MM-dd'T'HH:mm:ss"로. 하지만 LocalDateTimeSerializer를 사용하면 이 모든 것이 해결된다.
그리고 LocalDateTimeSerializer를 필요한 필드에 일일이 다는 대신, ObjectMapper에 JavaTimeModule을 추가하는 것이 훨씬 간결하고 간편하다.
오늘은 이 세 개의 어노테이션을 눈물을 머금고 지우며... 글을 마무리하겠다!