thymeleaf로 form data를 받고 jakarta의 @Valid annoatation을 이용해 데이터 유효성을 검증한 뒤 binding result에 해당 오류가 있을 때 나타나는 error.
@GetMapping("/register")
public String getRegisterPage(Model model) {
// set-up ID & name at first registration
model.addAttribute("registerUser",
RegisterUserDto.fromSession(
(SessionUser) httpSession.getAttribute("user")));
return "register.html";
}
@PostMapping("/register")
public String register(@Valid final RegisterUserDto registerUserDto,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
return "register.html";
}
model.addAttribute("user",
logInService.register(registerUserDto));
return "redirect:/"+registerUserDto.getUserId();
}
대충 연관된 컨트롤러 코드는 위와 같았고
register.html에서는 form 태그에 th:object="${registerUser}", 해당하는 input 태그에 th:field="${name}" 정도로 해줬다.
@ModelAttribute 의 역할 : name으로 model에 바인딩
@PostMapping을 통해 서버로 보내진 Form data는 스프링의 컨버터가 받은 데이터에 맞게 object로 매핑시켜준다.
Content Type과 @PostMapping
시작 전에 이 글은 아래 블로그 글을 참고해서 작성되었음을 미리 알린다. [Spring] Post 요청과 Content-Type의 관계 도움이 되시면 '광고'를 한번씩 눌러시면 감사하겠습니다 :) 실무에서 RestAPI를 만들
buchu-doodle.tistory.com
마침 직전에 공부하던 것이 나와서 괜히 반갑.
아무튼, @RequestBody를 붙이지 않은 지금 상황에선 자동으로 @ModelAttribute가 controller method의 DTO 객체 앞에 붙어 form data를 registerUserDto타입의 객체로 바꿔줬다.
문제는 이 dto의 데이터를 다시 register.html에 바인딩할때 발생한다. thymeleaf의 form data를 받기 위해 최초로 model을 통해 빈껍데기 객체를 보내주는 과정이 필요한데, 위에 올린 컨트롤러 코드에는 그 과정이 없다. 결국 스프링이 알아서 그 과정을 수행하게 된다. @ModelAttribute의 기능이랄까. 그때 key 값은따로 name을 설정해주지 않을 경우 bean name으로 설정된다. 위의 경우에는 @Valid 되는 객체의 타입이 RegisterUserDto이므로
model.addAttribute("registerUserDto", registerUserDto);
위 코드와 같은 과정이 이뤄지게 되고, th:object="${registerUser}"로 객체를 찾는 thymeleaf의 입장에선 당연히 오류가 날 수밖에 없는 것이었다.
결국 뭐다? @ModelAttribute의 name이 설정되지 않아서 일어난 오류이다.
@PostMapping의 method parameter을 다음과 같이 수정하자.
@PostMapping("/register")
public String register(@Valid @ModelAttribute("registerUser") final RegisterUserDto registerUserDto,
BindingResult bindingResult,
Model model)