상황
메뉴 목록 조회 API에 Redis 캐싱을 적용했다. 첫 번째 요청은 정상적으로 동작했지만, 두 번째 요청에서 아래 에러가 발생하며 캐시 히트가 실패했다.
Cannot construct instance of `MenuResponse`
(no Creators, like default constructor, exist)
캐시 히트 후 역직렬화 실패로 캐시 미스로 폴백되어 DB 조회가 발생했다.
원인 분석
Jackson이 JSON을 Java 객체로 역직렬화할 때 기본 전략은 이렇다.
1. 기본 생성자로 빈 객체 생성
2. 리플렉션으로 필드에 값 주입
MenuResponse는 정적 팩토리 메서드 from()으로만 생성하도록 설계돼 있어 기본 생성자가 없고 setter도 없다. Jackson이 역직렬화할 방법을 찾지 못해 예외가 발생한 것이다.
해결 방법 비교
두 가지 선택지를 검토했다.
첫 번째는 @NoArgsConstructor를 추가하는 방법이다. 구현이 단순하지만 final 키워드를 제거해야 해서 불변성이 깨진다. 빈 객체 생성이 가능해져 정적 팩토리 메서드로 생성 방식을 통제하려는 설계 의도도 퇴색된다.
두 번째는 @JsonCreator를 사용하는 방법이다. Jackson에게 역직렬화 시 사용할 생성자를 명시적으로 지정하는 방식이다. 코드가 다소 복잡해지지만 불변성과 생성 방식 통제를 유지할 수 있다.
나의 선택
@JsonCreator를 사용해 불변성과 설계 일관성을 유지했다.
@Getter
public class MenuResponse {
private final Long id;
private final String name;
private final Long price;
@JsonCreator
private MenuResponse(
@JsonProperty("id") Long id,
@JsonProperty("name") String name,
@JsonProperty("price") Long price) {
this.id = id;
this.name = name;
this.price = price;
}
public static MenuResponse from(Menu menu) {
return new MenuResponse(menu.getId(), menu.getName(), menu.getPrice());
}
}
MenuResponse는 DB에서 읽어온 데이터를 클라이언트에 전달하는 DTO다. 한 번 생성된 뒤 값이 바뀔 이유가 없으므로 불변성을 지키는 게 맞다. 코드가 조금 복잡해지는 건 감수할 만한 트레이드오프라고 판단했다.
결과
1차 요청 시 캐시 미스, 2차 요청 시 캐시 히트 정상 동작

인사이트
Redis에 객체를 JSON으로 직렬화해서 캐싱할 때는 역직렬화 가능 여부를 반드시 확인해야 한다. 불변 객체를 캐싱할 때는 @JsonCreator로 역직렬화 생성자를 명시적으로 지정하는 방식이 설계 의도를 지키면서 문제를 해결하는 방법이다.
'Projects > [Spring] Coffee Shop Project' 카테고리의 다른 글
| [트러블슈팅] 포인트 충전이 안되는 문제 해결하기 (0) | 2026.04.05 |
|---|---|
| [JWT] 현재 인증 방식 분석 — 완전한 Stateless인가? (0) | 2026.04.05 |
| [트러블슈팅] Spring Boot 4.x 업그레이드 시 Jackson 패키지 변경 주의하기 (0) | 2026.04.05 |
| [트러블슈팅] UnnecessaryStubbingException 해결하기 (0) | 2026.04.05 |
| [트러블슈팅] soft delete된 메뉴의 인기 랭킹 처리, 어떻게 할까? (0) | 2026.04.05 |