[트러블 슈팅] 요청 값 Dto에 어떻게 매핑되는가
문제 상황
Controller
@PostMapping("/save")
public ApiResponse<Long> save(@RequestBody FcmTokenSaveRequestDto requestDto,
@AuthenticationPrincipal UserDetails userDetails) {
return ApiResponse.success(HttpStatus.OK, fcmTokenMemberService.save(requestDto, userDetails.getUsername()));
}
문제 발생 DTO
@Getter
@RequiredArgsConstructor
public class FcmTokenSaveRequestDto {
private final String fcmToken;
public FcmToken toEntity(Member member) {
return FcmToken.builder()
.token(fcmToken)
.member(member)
.build();
}
}
현재 프론트에서 "fcmToken" : String 을 요청보내면 dto에 매핑되어 save 로직을 실행할 줄 알았지만
400 Bad Request 에러가 발생했다.
400 에러는 클라이언트의 요청을 서버가 거절하는 것이기 때문에 프론트 코드를 살펴보았지만 문제가 없었기에
값을 요청받는 dto에 문제가 있다 판단하고 @RequestBody Dto dto 대신 @RequestBody String fcmToken으로 재시도 해보았다.
코드 변경 후 제대로 실행되는 것을 확인하고 Dto의 문제라는 것을 확인하였다.
Dto에 어떤 문제가 있나 살펴보는 도중 현재 Dto 형태는 record로 변환 가능하였기에 혹시나 변환 후 실행해 보니 잘 동작하는 것을 확인하였다.
그럼 record 클래스와 현재 내 dto 클래스의 차이가 뭐지? 라고 생각을 해보았다.
1. getter 가 있는가? - [O]
2. final로 선언된 멤버필드를 매개변수로 가지는 생성자가 있는가? - [O]
3. equals 가 있는가? - [X]
4. hashcode 가 있는가? - [X]
5. toString 이 있는가? - [X]
그렇다면 @RequestBody 로 넘어온 값을 dto에 매핑하는데에 3~5번이 필요한가? 를 알아보았다.
결론부터 말하면 @RequestBody로 넘어온 요청을 dto로 변환해주는 것에는 getter 또는 setter가 필요하지
3~5번이 필요하지는 않다.
RequestDto에서 @Getter를 쓰는 이유
결론부터 얘기하자면 Spring에서의 Binding을 위해 쓰인다.
Binding은 사용자 입력 값을 객체의 필드에 매핑하는 과정을 말하는데, 웹페이지에서 입력된 값이 dto를 통해 서버로 넘겨질 때, Binding을 통해 dto의 객체로 넘겨진다.
@RequestBody는 HTTP 요청의 본문(body) 데이터를 자바 객체로 변환할 때 사용된다.
이 때 @RequestBody에서 본문 데이터인 json을 DTO 객체로 변환하는 과정을 역직렬화라고 한다.
그렇다면 @Getter 를 쓰는 이유는 무엇인가
HttpMessageConverter
동작 과정
1. Dispatcher Servlet에서 해당 요청을 Handler Adapter로 전달한다.
2. Handler Adapter은 ArgumentResolver를 호출하여 요청을 핸들러(컨트롤러)에 전달한다. 이때 Argument Resolver에서 HttpMessageConverter를 사용하여 요청(json)을 객체(dto)로 만든다. <역직렬화>
3. 만들어진 객체를 핸들러(컨트롤러)의 매개면수에 전달하고 핸들러가 동작한다.
4. 만약 핸들러가 반환할 값이 있다면 ReturnValueHandler에서 HttpMessageConverter를 이용해 객체를 응답(json)으로 만들어 반환한다. <직렬화>