쇼핑몰 v1을 Mybatis로 구현하는 프로젝트에서 Category 생성을 위해 RequestDTO로 POST 요청을 보냈는데 400 Bad Request Error 가 떴습니다.
오류 메시지는 다음과 같습니다.
03-29 14:10:34.937 WARN 2744 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of
org.store.clothstar.category.dto.CreateCategoryRequest
(although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance oforg.store.clothstar.category.dto.CreateCategoryRequest
(although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 2, column: 5]]
해당 오류는 HTTP 요청의 본문에서 JSON 데이터를 읽을 때 발생한 문제입니다.
더 구체적으로, com.fasterxml.jackson.databind.exc.MismatchedInputException
오류는 JSON 데이터를 Java 객체로 변환할 때 예상되는 형식과 실제 형식이 일치하지 않아 발생한 것입니다.
근본적인 원인은 jackson library가 기본 생성자가 없는 모델을 생성하는 방법을 모르기 때문입니다.
따라서 요청의 @RequestBody로 넘어오는 DTO 객체에 기본 생성자를 추가해 주거나, 직접 역직렬화할 생성자를 지정해줍니다.
🎨 Jackson
Jackson은 Java에서 객체를 JSON 문자열을 변환(역직렬화)하거나 JSON 문자열을 객체로 변환(직렬화)하는 기능을 제공하는 라이브러리입니다.
Jackson은 기본 생성자를 통해 JSON 데이터를 역직렬화 합니다.
기본적으로는 기본 생성자가 있어야 리플렉션을 통해 객체를 생성합니다.
따라서 객체에 기본 생성자가 없는 경우 Jackson에서InvalidDefinitionException
예외를 발생시킵니다.
Jackson2HttpMessageConverter
@RequestBody
로 JSON 데이터가 넘어오면 Java Object 변환은 Jackson2HttpMessageConverter
에서 담당Jackson2HttpMessageConverter
의 ObjectMaper
를 사용해서 Object로 전환JSON 데이터가 아닌 Query Parameter
를 받음
그래서 Jackson2HttpMessageConverter 가 아니라 WebDataBinder
를 사용
@NoArgsConstructor
로 public 기본생성자 추가 다음과 같이 @RequestBody로 들어오는 객체에 @NoArgsConstructor
를 추가합니다.
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CreateCategoryRequest {
@NotBlank(message = "카테고리 타입을 입력해주세요.")
private String categoryType;
public Category toCategory() {
return Category.builder()
.categoryType(categoryType)
.build();
}
}
🎨
@NoArgsConstructor
와@AllArgsConstructor
순서
@NoArgsConstructor
와@AllArgsConstructor
를 동시에 쓸 경우
@AllArgsConstructor
를 먼저 써야 합니다!
또한 Lombok 사용시 주의점은 다음 링크를 참고하세요!
아래와 같이 기본 생성자를 추가합니다.
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CreateCategoryRequest {
@NotBlank(message = "카테고리 타입을 입력해주세요.")
private String categoryType;
protected CreateCategoryRequest() {
}
public Category toCategory() {
return Category.builder()
.categoryType(categoryType)
.build();
}
}
만약 엄격하게 객체를 불변으로 두기 위해서 final을 유지하고 싶다면 @JsonCreator 와 @JsonProperty 애너테이션을 사용하여 final 변수에 값을 주입할 수 있습니다.
@JsonCreator
애너테이션은 Jackson이 역직렬화할 생성자를 지정해 준다.
@JsonProperty
애너테이션은 Jackson이 역직렬화할 JSON 데이터의 이름을 지정해 준다.
public class BuddyDto {
@Getter
@Builder
public static class PostRecruitment {
private final String title;
private final String content;
private final String travelNationality;
private final String travelStartDate;
private final String travelEndDate;
@JsonCreator
public PostRecruitment(@JsonProperty("title") String title,
@JsonProperty("content") String content,
@JsonProperty("travelNationality") String travelNationality,
@JsonProperty("travelStartDate") String travelStartDate,
@JsonProperty("travelEndDate") String travelEndDate) {
this.title = title;
this.content = content;
this.travelNationality = travelNationality;
this.travelStartDate = travelStartDate;
this.travelEndDate = travelEndDate;
}
}
...
}
방법 1을 택한 이유는 다음과 같습니다.
방법 1을 택하여 적용하였을 때 다음과 같이 JSON 역직렬화가 잘 작동하여 해결된 모습을 볼 수 있습니다.