Spring 기초/Spring 기초 지식
[Spring] @ModelAttribute 바인딩 실패와 해결
Stark97
2024. 2. 1. 02:21
반응형
@ModelAttribute에서 데이터 바인딩을 위해서 꼭 필요한 메서드가 존재한다.
📌 서론
컨트롤러에서 매게 변수로 dto객체를 받을 때 발생하는 바인딩 오류는 정말 간단하게 해결되지만 자주 발생하는 오류이기도 하다. 그만큼 바인딩에 대해서는 신경 쓰지 않고 알아서 적용되겠지?라는 생각을 가지고 어노테이션을 사용하게 되기 때문인데 수많은 api를 만들어 보게 되지만 바인딩도 @RequestBody인지 @ModelAttribute인지에 따라 그 방식이 다르다. 이번에는 @ModelAttribute를 사용했을 때 나의 실수로 인해 바인딩에 실패했던 내용을 소개하고자 한다.
1. 오류가 발생한 상황
오류가 발생한 코드 분석
- 아래의 API에 요청을 보냈을 때 바인딩 오류가 발생했다.
/**
* 마이페이지 수정
*/
@PostMapping("/member/myPage/update")
public ResponseEntity<ResponseDto<Void>> update(
@Valid
@ModelAttribute UpdateMyPageRequestDto dto
) {
MultipartFile profileImage = dto.getProfileImage();
myPageUseCase.updateMyPage(myPageConverter.updateRequestDtoToDomain(dto), profileImage);
return ResponseEntity.ok(
ResponseDto.success()
);
}
이 API가 요청으로 받는 dto는 다음과 같이 작성했다.
@ToString
@Getter
@NoArgsConstructor
public class UpdateMyPageRequestDto {
@NotBlank
private String nickname; // 닉네임
@Size(max = 300)
private String introduction; // 한줄소개
private MultipartFile profileImage; // 프로필 이미지
private Integer deleteFileOrder; // 삭제할 file order
private String birth;
private String gender;
}
안드로이드에서는 아래의 인터페이스로 retrofit 요청을 보냈다.
interface MyPageService {
// 프로필 수정
@Multipart
@POST("/member/myPage/update")
suspend fun updateProfile(
@Part("nickname") nickname: RequestBody,
@Part("introduction") introduction: RequestBody?,
@Part profileImage: MultipartBody.Part?,
@Part("deleteFileOrder") deleteFileOrder: RequestBody?,
@Part("birth") birth: RequestBody?,
@Part("gender") gender: RequestBody?
): Response<ResponseDto<Void>>
여기까지의 내용만 봤을 때 무엇 때문에 바인딩 오류가 발생했는지 알겠는가? 지금부터 발생한 오류를 분석해 보자
2. 오류 분석
오류 로그 분석
- 지금 api에 요청을 보내면 오류가 발생한다. 오류 로그에는 아래와 같은 정보가 남아있었다.
MethodArgumentNotValidException occurred
Validation failed for argument [0] in public org.springframework.http.ResponseEntity<com.recipia.member.adapter.in.web.dto.response.ResponseDto<java.lang.Void>>
[Field error in object 'updateMyPageRequestDto' on field 'nickname': rejected value [null]; codes
arguments []; default message [nickname]]; default message [공백일 수 없습니다]]
닉네임은 공백일 수 없다는 @Valid 에러 발생
- 이것만 봐서는 @Valid 오류라고 생각할 수 있지만 근본적인 문제는 nickname을 세팅하지 못하는 바인딩 오류다.
요청 디버깅 진행
- 이 오류를 파악하기 위해 내가 정말 nickname을 보내지 않았는지 안드로이드에서 요청을 보내면서 디버깅을 해봤는데 닉네임과 다른 데이터를 문제없이 잘 보내고 있다는 것을 알 수 있었다.
📌 오류 분석 결과
안드로이드에서 데이터를 제대로 보내는 것으로 봐선 스프링에서 @ModelAttribute를 사용하여 멀티파트 요청 데이터를 바인딩할 때 발생한 문제인 것으로 보인다.
3. 오류 해결
오류 해결
- @ModelAttribute에서 발생한 오류라고 판단한 직후 바로 Dto객체를 확인했다. Dto에는 @Getter 어노테이션만이 사용되고 있었는데 @ModelAttribute는 @Setter가 필요하다는 것을 순간 놓쳤던 내 잘못이었다.
- 이 오류를 해결하기 위해 dto 클래스에 @Setter를 포함하는 Lombok의 @Data를 적어줬더니 안드로이드에서 요청으로 보낸 데이터를 잘 바인딩하는 것을 확인할 수 있었다.
/**
* 마이페이지 수정 요청을 담당하는 request dto
*/@ToString
@Data
@NoArgsConstructor
public class UpdateMyPageRequestDto {
@NotBlank
private String nickname; // 닉네임
@Size(max = 300)
private String introduction; // 한줄소개
private MultipartFile profileImage; // 프로필 이미지
private Integer deleteFileOrder; // 삭제할 file order
private String birth;
private String gender;
}
4. 마무리
결론
- 에러 메시지를 다시 한번 보면 nickname 필드에 대한 바인딩 실패가 언급되었기 때문에, 이는 nickname 필드에 값을 설정할 세터 메서드가 없어서 발생한 것이라는 것을 알 수 있다.
- 처음에는 @Valid의 오류라고 생각해서 조금 헷갈렸는데 @NotBlank는 단지 nickname 필드에 값이 성공적으로 바인딩된 후, 그 값이 빈 문자열이 아닌지 검증하는 과정에서 사용되는 것이지, 바인딩 과정 자체와는 직접적인 관련이 없다.
- 따라서, 문제의 핵심은 @ModelAttribute가 객체에 데이터를 바인딩하는 과정에서 @Setter 메서드가 필요하다는 것이고, nickname 값이 null로 나타나는 것은 nickname 필드에 데이터를 설정할 세터 메서드가 없기 때문에 발생한 바인딩 문제였던 것이다.
반응형