Projects/[Spring] Coffee Shop Project

[트러블슈팅] : Redis 캐시 역직렬화 실패 - 기본 생성자가 없는 경우

montmer27 2026. 4. 5. 04:39

상황

메뉴 목록 조회 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로 역직렬화 생성자를 명시적으로 지정하는 방식이 설계 의도를 지키면서 문제를 해결하는 방법이다.