에러 발생과 원인
통계 정보를 캐싱하는 과정에서 다음과 에러가 발생했습니다.
Java 8 date/time type java.time.LocalDateTime not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
번역기를 돌려보면 "Java 8 날짜/시간 유형 java.time.LocalDateTime은 기본적으로 지원되지 않습니다. 모듈 "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"을 추가하세요." 라고 해석이 된다.
또한, 에러 로그를 보면 아래와 같은 내용도 있습니다.
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsBytes → at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.serialize
이러한 흐름으로 보아, LocalDateTime을 Redis에 저장하려는 과정에서 Jackson이 해당 타입을 직렬화하지 못해 오류가 발생한 것으로 추측할 수 있습니다.
즉, 캐시에 저장하려던 객체에는 LocalDateTime 타입이 있는데, Jackson이 이걸 변환할 수 있는 설정이 없어서 에러가 난 것이다.


해결
해결 방법은 에러 메시지에서 힌트를 줬듯이, 먼저 build.gradle 파일에 관련 의존성을 추가하고, LocalDateTime 같은 날짜/시간 타입을 Jackson이 인식하고 처리할 수 있도록 Java Time 모듈을 등록해줘야 합니다.
하지만 현재 프로젝트에서는 Redis 캐싱 대상이 아직 BoardStats 하나뿐이기 때문에, 굳이 ObjectMapper 설정을 추가하지 않고도 날짜 타입을 안전하게 처리할 수 있는 방법이 있지 않을까? 라는 고민을 하였고, 그 고민을 기반으로 아래와 같이 해결하였습니다.
Redis에는 LocalDateTime을 String으로 변환한 DTO 객체인 BoardStatsCacheDto를 저장하고,
서비스에서는 기존 도메인 객체인 BoardStats를 반환하도록 구성했습니다.
public Map<String, BoardStats> getWithFallback(
List<String> qnaIds,
Function<List<String>, List<BoardStats>> fallback
) {
List<String> redisKeys = qnaIds.stream()
.map(id -> KEY_PREFIX + id)
.toList();
List<Object> cachedResults = redisTemplate.opsForValue().multiGet(redisKeys);
Map<String, BoardStats> resultMap = new HashMap<>();
List<String> missedQnaIds = new ArrayList<>();
for (int i = 0; i < qnaIds.size(); i++) {
Object cached = cachedResults.get(i);
String qnaId = qnaIds.get(i);
if (cached instanceof BoardStatsCacheDto statsCacheDto) {
resultMap.put(qnaId, statsCacheDto.toModel());
} else {
missedQnaIds.add(qnaId);
}
}
if (!missedQnaIds.isEmpty()) {
List<BoardStats> fallbackStats = fallback.apply(missedQnaIds);
for (BoardStats stats : fallbackStats) {
String key = KEY_PREFIX + stats.getQnaId();
BoardStatsCacheDto statsCacheDto = BoardStatsCacheDto.from(stats);
redisTemplate.opsForValue().set(key, statsCacheDto, CACHE_TTL, TimeUnit.SECONDS);
resultMap.put(stats.getQnaId(), stats);
}
}
return resultMap;
}
여기서 BoardStatsCacheDto.from(stats)를 통해 BoardStats의 시간 관련 필드를 String으로 변환한 후 Redis에 저장합니다.
반대로 Redis에서 조회한 값은 BoardStatsCacheDto.toModel()을 통해 다시 도메인 객체인 BoardStats로 변환합니다.
Redis에는 DTO를 저장하고, 서비스에는 도메인 객체를 반환하는 이유
서비스에 BoardStats를 반환하는 이유는, 이후 통계 필드를 MongoDB에 flush해야 할 때 타입이 정확히 맞아야 하기 때문입니다.
만약 BoardStatsCacheDto를 그대로 반환하게 되면, lastUpdated 같은 필드는 String 타입이라 시간 비교나 날짜 계산을 하려 할 때 LocalDateTime이 아니어서 오류가 발생할 수 있습니다.
또, 서비스에서는 BoardStats 객체를 기준으로 동작하기 때문에 기존 코드 변경 없이 처리할 수 있다는 장점이 있습니다.
'개발' 카테고리의 다른 글
| Redis 캐시 구조 개선 : Redis Hash 적용하기 (1) | 2025.06.24 |
|---|---|
| MongoDB 도큐먼트 분리와 Redis 캐싱으로 게시판 성능 최적화 (2) | 2025.05.26 |
| 대형 Document 분리와 비지니스 로직 개선 중 발생한 성능이슈 : [MongoDB 성능 리팩토링] (1) | 2025.04.29 |
| OAuth2.0 로그인 성공시 토큰 관리 (0) | 2025.04.05 |
| ec2 환경에서 도커 스크립트 작성 (0) | 2025.03.21 |