Spring Boot에서 발생하는 직렬화 오류 해결 가이드

서론

Spring Boot 애플리케이션 개발 중 Redis나 RabbitMQ와 같은 메시지 브로커를 사용하다 보면 예기치 않은 직렬화 오류를 마주할 수 있습니다. 특히 데이터 직렬화 및 역직렬화 과정에서 발생하는 문제는 시스템의 안정성을 해칠 수 있습니다. 이번 포스팅에서는 이러한 오류의 발생 원인과 해결 방법, 그리고 이를 이해하기 위한 이론적인 배경까지 자세히 살펴보겠습니다.


1. 문제 상황

Spring Boot로 개발된 문자 발송 서비스에서 다음과 같은 직렬화 오류가 발생했습니다.

Could not read JSON: Unrecognized token 'TokenResDto': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')

 

이 오류는 Redis에서 데이터를 역직렬화하는 과정에서 발생했으며, JSON 파싱 중 인식할 수 없는 토큰 TokenResDto가 발견되었다는 내용입니다.


2. 오류의 원인 분석

2.1 직렬화와 역질렬화 개념

  • 직렬화(Serialization): 객체를 바이트 스트림으로 변환하여 저장하거나 전송할 수 있도록 하는 과정입니다.
  • 역직렬화(Deserialization): 바이트 스트림을 다시 객체로 변환하는 과정입니다.

2.2 오류 발생의 근본적인 원인

  • 데이터 형식 불일치: Redis에 저장된 데이터가 JSON 형식이 아닌, 객체의 toString() 결과로 저장되었습니다.
  • 역직렬화 실패: Spring Data Redis는 데이터를 JSON으로 역직렬화하려고 시도하지만, 실제 데이터는 JSON 형식이 아니어서 파싱에 실패합니다.

2.3 기존 코드 분석

redisTemplate.opsForValue().set("token", redisTokenDtoValue(tokenResDto));

public static String redisTokenDtoValue(TokenResDto tokenResDto) throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(tokenResDto);
    return objectMapper.readValue(json, TokenResDto.class).toString();
}
  • redisTokenDtoValue 메서드에서 객체를 JSON으로 직렬화했다가 다시 객체로 역직렬화한 후 toString()을 호출하여 Redis에 저장하고 있습니다.
  • toString() 메서드의 결과는 JSON 형식이 아니므로, Redis에 저장된 데이터가 JSON이 아닙니다.

3. 해결 방법

3.1 불필요한 역질렬화와 toString() 제거하기

public static String redisTokenDtoValue(TokenResDto tokenResDto) throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    return objectMapper.writeValueAsString(tokenResDto);
}
  • 객체를 JSON 문자열로 직렬화한 후 바로 반환합니다.
  • Redis에는 이 JSON 문자열을 저장합니다.

3.2 RedisTemplate의 Serializer 설정 변경

@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
    RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
    template.setConnectionFactory(connectionFactory);
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    return template;
}
  • GenericJackson2JsonRedisSerializer를 사용하여 RedisTemplate이 자동으로 객체를 JSON으로 직렬화/역직렬화하도록 설정합니다.
  • 이를 통해 코드에서 별도의 직렬화 로직이 필요 없어집니다.

3.3 데이터 저장 및 조회 코드

데이터 저장

TokenResDto tokenResDto = new TokenResDto(...);
redisTemplate.opsForValue().set("token", tokenResDto);

데이터 조회

TokenResDto tokenResDto = redisTemplate.opsForValue().get("token");
  • RedisTemplate의 Serializer 설정으로 인해 객체를 직접 저장하고 조회할 수 있습니다.

4. 이론적 배경 및 추가 고려사항

4.1 Jackson 라이브러리

  • ObjectMapper: Jackson 라이브러리에서 JSON 직렬화와 역직렬화를 담당하는 핵심 클래스입니다.
  • 주의사항: toString() 메서드는 JSON 직렬화와 무관하므로, 객체를 JSON으로 변환하려면 반드시 writeValueAsString()을 사용해야 합니다.

4.2 Rdis의 데이터 형식

  • Redis는 기본적으로 바이트 배열로 데이터를 저장합니다.
  • 데이터의 직렬화 형식을 통일하지 않으면 역직렬화 시 오류가 발생할 수 있습니다.

4.3 Serializer 설정의 중요성

  • RedisTemplate의 Serializer 설정은 데이터의 직렬화와 역직렬화를 자동으로 처리해주므로, 올바른 설정이 필요합니다.
  • 키와 값의 Serializer를 명시적으로 설정하여 예기치 않은 오류를 방지합니다.

4.4 예외 처리

  • 데이터 처리 중 예외가 발생할 경우를 대비하여 적절한 예외 처리 로직을 구현해야 합니다.
  • 특히 메시지 리스너나 데이터 접근 로직에서의 예외 처리는 시스템 안정성에 중요합니다.

결론

Spring Boot 애플리케이션에서 Redis나 메시지 브로커를 사용할 때, 데이터의 직렬화 형식을 정확히 이해하고 일관성 있게 유지하는 것이 중요합니다. 

  • 객체를 JSON으로 직렬화할 때는 ObjectMapper의 writeValueAsString() 메서드를 사용한다.
  • RedisTemplate의 Serializer 설정을 통해 데이터 직렬화/역직렬화를 자동화한다.
  • 데이터 저장 및 조회 시 일관된 형식을 유지하여 역직렬화 오류를 방지한다.

참고