아.. 어제는 그제의 밀린 TIL을 작성하고 너무나도 귀찮아서 어제 TIL을 작성하지 않았었는데 친절하신 매니저님께서 슬랙에 친히 내 이름을 박제시켜주셔서 귀찮음을 무릅쓰고 어제의 TIL을 오늘 작성해보려고 한다. 오늘의 주제는 11월 17일 TIL에 ModelAttribute 관련된 이슈에서 해결 원인을 찾지 못했었는데 이 이슈에 대해 이야기해보려고 한다. TIL 시작.
프로젝트 VONGOLE에서 이미지를 업로드할 때, dto에 @ModelAttribute라는 어노테이션을 붙여서 요청을 들여온다. 이미지 업로드 부분은 성민님이 구현을 해주셨는데 나는 지금까지 RequestPart나 RequestParam을 이용해서 이미지를 받아왔는데 성민님 덕분에 ModelAttribute를 알게 되었고 나름 구글링을 하면서 공부를 해보았다.
@RestController
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
public ResponseDto<BoardResponse> createBoard(@AuthenticationPrincipal UserDetailsImpl userDetails,
@RequestParam String title,
@RequestParam String content,
@RequestParam String author,
@RequestPart MultipartFile multipartFile) {
return boardService.createBoard(userDetailImpl.getMember(), title, content, author, multipartFile)
}
}
예를 들어서 RequestParam을 사용해서 이미지를 받을 경우, 일일히 사용자의 요청을 받아오게 된다. 이 방법의 단점이라 하면 매개변수가 많아지면 많아질수록 코드가 복잡해지고 순서가 바뀌는 개발자 실수가 일어날 수 있다.
반면 ModelAttribute의 경우에는 1대1 매핑을 해주는 RequestParam과는 달리 객체 매핑을 해주기 때문에 코드가 한결 깔끔해지고 순서가 바뀔 위험도 없다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/boards")
public class BoardController {
private final BoardService boardService;
@PostMapping()
public ResponseDto<BoardCreateResponse> createBoard(@AuthenticationPrincipal UserDetailsImpl userDetails,
@ModelAttribute @Valid BoardRequest boardRequest) throws IOException {
return boardService.createBoard(userDetails.getMember(), boardRequest);
}
하지만 ModelAttribute를 사용 시 유의해야 할 점이 있다.
ModelAttribute를 사용할 때 객체 매핑이 저절로 되는 것이 아니라 어떤 설정을 해줘야만 한다. 코드를 보며 예를 들어 설명해 보면,
@Getter
@AllArgsConstructor
public class BoardRequest {
@NotEmpty(message = "빈 칸을 채워 주세요.")
private String title;
@NotEmpty(message = "빈 칸을 채워 주세요.")
private String content;
private MultipartFile boardImage;
...
}
@ModelAttribute 설정을 해준 dto를 객체로 바인딩해주기 위해서는 두가지 방법이 있다.
@AllArgsConstructor를 이용해서 각 필드를 초기화한 객체를 생성한다.
@NoargsConstructor과 @Setter로 각각의 필드 초기화
이 방법은 기본 생성자로 객체를 생성한 후, 각 필드들을 setter로 초기화하는 방법으로 객체를 바인딩하게 된다.
@Getter
@NoArgsConstructor
@AllArgsConstructor
public BoardRequest {
...
}
나는 지금까지 그냥 습관적으로 위의 코드처럼 NoArgsConstructor와 AllArgsConstructor를 같이 붙여왔던 것 같다. 이렇게 코드를 짰더니 NullpointerException이 뜨게 되었다. 그 당시에는 도저히 뭐가 문제인지 모르겠어서 진구 님께 도움을 요청했었는데 생성자 중에 NoArgsConstructor를 이용해서 객체를 생성하게 된 것 같아서 초기화가 안되어 있어 NullpointerException이 뜬 것 같다고 알려 주셨다.
NoArgsConstructor 어노테이션을 제거함으로써 에러를 잡을 수 있었다.
생각이 담기지 않은 코드가 이렇게 무섭다. 새삼 다시 한 번 코드 한 줄 한 줄을 생각하면서 작성해야겠다고 생각했던 이슈였다.