문제 발생 배경
Spring 애플리케이션에서 Redis를 사용하여 Hashtag 객체를 캐시할 때 발생한 직렬화 문제를 다룹니다.
문제는 Hashtag 객체를 Redis에 저장하고, 저장된 데이터를 가져오는 과정에서 발생했습니다.
Jackson의 직렬화 문제와 Redis에서 데이터를 올바르게 조회하는 과정이 문제가 되었습니다.
문제 분석
- Redis 데이터 저장 타입 : Hashtag 서비스레이어에서 데이터가 Set 형식으로 반환하여 json 형식으로 직렬화와 역직렬화를 위한 적절한 설정 필요
- LocalDateTime과 같은 Java 8의 날짜/시간 타입을 직렬화 문제: Hashtag 객체에 포함된 LocalDateTime 필드가 Jackson이 기본적으로 처리할 수 없었습니다. 이를 해결하기 위해 jackson-datatype-jsr310 모듈을 추가해야 했습니다.
- StackOverflowError: Hashtag 와 Article 클래스가 양방향 관계를 갖고 있기 때문에, 직렬화 시에 서로 참조하게 되어 순환 참조 문제 발생
해결 방법
build.gradle 에 추가
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.4' // Jackson JSR310 모듈 추가
Java 8 LocalDateTime 타입을 직렬화하려면 jackson-datatype-jsr310 모듈을 사용해야 합니다. 이를 추가한 후, RedisConfig에서 ObjectMapper를 설정하여 날짜/시간 타입을 처리할 수 있도록 했습니다.
redisconfig 설정
@Bean
public RedisTemplate<String, Object> redisTemplate(ObjectMapper objectMapper) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
// Key는 StringRedisSerializer 사용
redisTemplate.setKeySerializer(new StringRedisSerializer());
// Value는 GenericJackson2JsonRedisSerializer 사용
GenericJackson2JsonRedisSerializer genericSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
redisTemplate.setValueSerializer(genericSerializer);
return redisTemplate;
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// Java 8 날짜/시간 API 처리 위한 모듈 등록
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
ObjectMapper에 javaTimeModule 을 등록하여 LocalDateTime 과 같은 java 8 날짜/시간 타입을 직렬화할 수 있게 합니다.
직렬화와 역직렬화 시 Set<Hashtag>를 처리할 수 있는 GenericJackson2JsonRedisSerializer를 사용하거나, Jackson2JsonRedisSerializer에 Hashtag 클래스를 매핑하는 방식을 확인해야 합니다.
GenericJackson2JsonRedisSerializer는 객체를 JSON 형태로 직렬화 합니다. 내부적으로는 ObjectMapper을 사용하여 위와 같이 ObjectMapper를 적용 할 수 있습니다.
Jackson2JsonRedisSerializer도 기본적으로 객체를 JSON 형태로 직렬화 해주지만 항상 타입을 지정 해 주어야 합니다.
2. hashtagservice 에서 hashtag 데이터 저장 및 조회
private final RedisTemplate<String , Object> redisTemplate;
private static final String HASHTAG_CACHE_KEY = "hashtags";
// Hashtag를 캐시하는 메서드
private void cacheHashtags(Set<Hashtag> hashtags) {
redisTemplate.opsForValue().set(HASHTAG_CACHE_KEY, hashtags);
}
// Redis에서 Hashtag 데이터 조회
public Set<Hashtag> getHashtagsFromCache() {
Object cachedData = redisTemplate.opsForValue().get("hashtags");
if (cachedData instanceof Set) {
return (Set<Hashtag>) cachedData;
}
return Set.of();
}
redis에 hashtags 데이터가 들어갔다..!
성공