개발 공부/트러블슈팅

@RequestBody로 객체를 받을 때 @Builder만 있으면 발생하는 400 에러 원인과 해결방법

빵다희 2024. 8. 25. 01:08

👾 문제식별

RequestDto 클래스 어노테이션 수정을 했다.

데이터 수정의 위험이 있는 @setter 대신 @builder로 변경한 뒤, 컨트롤러 테스트를 해봤는데 원인 모를 400 에러 발생.

{
  "timestamp": "2024-08-24T09:56:57.523+00:00",
  "status": 400,
  "error": "Bad Request",
  "path": "/notebook"
}

 


🔍 원인분석

우선 HttpMessageNotReadableException 가 무슨 에러인지 알아보자.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/HttpMessageNotReadableException.html

HttpMessageNotReadableException

 

HttpMessageConverter.read가 실패할 때 던져지는 exception이라는데

HttpMessageConverter.read를 자세히 살펴보면 

HttpMessageConverter.read

HttpMessageNotReadableException은 inputMessage를 clazz타입으로 변환하려다가 오류가 난거라고 한다.

나의 케이스는 json형식의 text를 RequestDto로 변환하다가 문제가 생긴것이다.

 


🤔 해결 방법 시도

controller

 

dto

  1. 가장 나중에 추가한 @Builder 삭제 : 저장 서비스 테스트할 때 필요해서 삭제 불가.
  2. 혹시나 역직렬화할때 필요한건가? 하고 @Setter 추가 : 동일오류(HttpMessageNotReadableException) 
  3. 또 혹시나? @NotBlank, @Valid 제거 : 동일오류
  4. @RequestBody를 쓸 때에는 기본 생성자가 필요하다고 해서 @NoArgsConstructor를 붙이려 했으나 @Builder와 충돌나면서 실패
  5. 그럴때에는 보통 @AllArgsConstructor를 같이 붙여주면 오류가 안나기 때문에 @NoArgsConstructor, @AllArgsConstructor 추가 : 성공

✅ 최종 해결 방법

dto 수정 : @NoArgsConstructor 추가, 빌더 생성자 추가

테스트 결과 : 200 저장 성공

 


💡 해결 방법 설명

결론은 Json 데이터의 역직렬화에 대해서 잘 알지못해서 발생한 문제이다.

 

1. @RequestBody를 사용하여 JSON 데이터를 객체로 변환할 때, Jackson은 기본적으로 기본 생성자를 사용하여 객체를 생성한다.

하지만 생성자가 없어도 역직렬화는 가능하다. 

매개변수 이름을 사용하여 Java 개체를 직렬화 및 역직렬화하는 방법을 제공하는 Java 라이브러리인 ParameterNamesModule

사용하면 된다. (스프링부트를 사용한다면 대개 추가 하는 spring-boot-starter-web에 해당 모듈이 주입되어있다.)

* json 역직렬화 참고 : https://weights-learn-develop.tistory.com/41

 

2. 기본생성자가 없는 객체에 @Builder를 붙이면 

Jackson은 생성자를 호출하지 않고 build() 메서드를 사용하여 객체를 생성하므로, 매개변수 이름을 사용할 수 없다.

매개변수 이름 기반으로 JSON 필드를 매핑하는 ParameterNamesModule은 동작이 불가하다.

 

3. 기본생성자를 추가해서 빌더패턴으로 객체 생성되는 걸 방지해야했다.

하지만 @builder와 @NoArgsConstructor를 동시에 사용하면 객체 생성 방식이 충돌되어 컴파일 오류가 발생한다.

그렇다고 이 오류를 해결하기 위해 @AllArgsConstructor를 붙이는 건 불필요하다고 생각했다.

 

4. @NoArgsConstructor를 붙이고 수동으로 필요한 필드를 매개변수로 하는 생성자를 만든 다음, 빌더로 지정해주었다.

 


📝 재발 방지 대책

  1. jackson의 역직렬화 하는 방법에 대해서 알아놓자
  2. 원래는 기본 생성자를 통하여 객체생성을 하였지만, 기본 생성자가 없어도 가능(공식문서에서는 @JsonProperty, 빌더 패턴 등 기본생성자 없이도 매핑되는 방법을 제시하고 있다.)

* jackson 공식문서 : https://github.com/FasterXML/jackson-databind/

 

728x90
반응형