@GetMapping("/findId")
public String findId(FindIdDto findIdDto) {
return "find_id";
}
@PostMapping("/findId")
public String findId(@Valid FindIdDto findIdDto, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "find_id";
}
Optional<SiteUser> optionalSiteUser = userService.findByEmail(findIdDto.getEmail());
if (optionalSiteUser.isEmpty()) {
bindingResult.rejectValue("email", "emailNotFound", "해당 이메일로 가입된 아이디가 없어요. 이메일 주소를 다시 확인해 주세요.");
} else {
String username = optionalSiteUser.get().getUsername();
model.addAttribute("username", username);
}
return "find_id";
}
<!-- layout.html을 상속 받도록 설정. -->
<html layout:decorate="~{layout}">
<!-- 부모 템플릿(layout.html)의 layout:fragment="content" 부분에 삽입되도록 설정. -->
<div layout:fragment="content" class="container my-3" style="max-width: 900px">
<h4 class="my-3 border-bottom pb-2">아이디 찾기</h4>
<form th:action="@{/user/findId}" method="post">
<div th:replace="~{binding_result_errors :: bindingResultErrorsFragment}"></div>
<div class="mb-3">
<label for="email" class="form-label">이메일</label>
<input type="email" id="email" name="email" class="form-control" placeholder="가입하신 이메일 주소를 입력해 주세요.">
</div>
<button type="submit" class="btn btn-outline-primary w-100">아이디 찾기</button>
</form>
<div th:if="${username}" class="alert alert-success mt-3 text-center">
<p>입력하신 이메일로 가입되어 있는 회원님의 아이디는 <strong th:text="${username}"></strong> 입니다.</p>
</div>
</div>
</html>
에러 메시지
org.thymeleaf.exceptions.TemplateProcessingException: Could not bind form errors using expression "all".
Please check this expression is being executed inside the adequate context
(e.g. a <form> with a th:object attribute)
(template: "binding_result_errors" - line 1, col 116)
데이터 유효성 검증(Validation) 과정.
- 클라이언트 요청 : 사용자가 웹 폼을 통해 데이터를 전송.
↓- 컨트롤러 메서드 호출 : 스프링 MVC가 요청을 받아 컨트롤러의 메서드를 호출.
↓- 데이터 바인딩 및 유효성 검증 : 스프링은 폼 데이터를 컨트롤러 메서드의 파라미터(
Ex. DTO 객체)에 자동으로 바인딩(binding)함.
이때 @Valid 에노테이션이 붙어 있으면 스프링은 Hibernate Validator를 통해 유효성 검증 규칙(예: @Email, @NotBlank)을 실행.
↓- 검증 결과는 BindingResult 객체에 저장.
이 객체는 어떤 필드에서 어떤 종류의 오류가 발생했는지에 대한 모든 정보를 담고 있음.
↓- Thymeleaf 템플릿 렌더링 : 컨트롤러가 뷰(View)템플릿 이름을 반환하면 타임리프 템플릿 엔진이 렌더링을 시작.
↓- 템플릿에
th:object="${DTO 객체}"이 있으면 타임리프는 이 폼이DTO 객체와 연결되어 있다는 것을 인지함.
↓- 이때 BindingResult 객체가 함께 전달되면 타임리프는
th:object와BindingResult를 연결하여th:errors와 같은 속성을 통해 오류 메시지를 렌더링하여 출력함.
th:object="${...}" 누락.<form> 태그에 th:object 속성이 지정되어 있어야 함.th:object 속성은 폼이 어떤 객체와 바인딩될지 Thymeleaf에게 알려주는 역할.th:object가 없어서 폼(<form>) 태그에 th:object="${....}"이 없다면 타임리프는 BindingResult를 처리할 객체를 찾지 못해서(또는 BindingResult가 어느 객체에 대한 오류인지 알 수 없어서)에러가 발생한 것.<!-- layout.html을 상속 받도록 설정. -->
<html layout:decorate="~{layout}">
<!-- 부모 템플릿(layout.html)의 layout:fragment="content" 부분에 삽입되도록 설정. -->
<div layout:fragment="content" class="container my-3" style="max-width: 900px">
<h4 class="my-3 border-bottom pb-2">아이디 찾기</h4>
<form th:action="@{/user/findId}" th:object="${findIdDto}" method="post">
<div th:replace="~{binding_result_errors :: bindingResultErrorsFragment}"></div>
<div class="mb-3">
<label for="email" class="form-label">이메일</label>
<input type="email" th:field="*{email}" class="form-control" placeholder="가입하신 이메일 주소를 입력해 주세요.">
</div>
<button type="submit" class="btn btn-outline-primary w-100">아이디 찾기</button>
</form>
<div th:if="${username}" class="alert alert-success mt-3 text-center">
<p>입력하신 이메일로 가입되어 있는 회원님의 아이디는 <strong th:text="${username}"></strong> 입니다.</p>
</div>
</div>
</html>