[Spring] Error - Could not bind form errors using expression "all". Please check this expression is being executed inside the adequate context

하쮸·2025년 9월 23일

Error, Why, What, How

목록 보기
34/63

1. 에러.


1-1. 현재 상황.

    @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)

1-2. 원인 & 해결.

데이터 유효성 검증(Validation) 과정.

  1. 클라이언트 요청 : 사용자가 웹 폼을 통해 데이터를 전송.
  2. 컨트롤러 메서드 호출 : 스프링 MVC가 요청을 받아 컨트롤러의 메서드를 호출.
  3. 데이터 바인딩 및 유효성 검증 : 스프링은 폼 데이터를 컨트롤러 메서드의 파라미터(Ex. DTO 객체)에 자동으로 바인딩(binding)함.
    이때 @Valid 에노테이션이 붙어 있으면 스프링은 Hibernate Validator를 통해 유효성 검증 규칙(예: @Email, @NotBlank)을 실행.
  4. 검증 결과는 BindingResult 객체에 저장.
    이 객체는 어떤 필드에서 어떤 종류의 오류가 발생했는지에 대한 모든 정보를 담고 있음.
  5. Thymeleaf 템플릿 렌더링 : 컨트롤러가 뷰(View)템플릿 이름을 반환하면 타임리프 템플릿 엔진이 렌더링을 시작.
  6. 템플릿에 th:object="${DTO 객체}"이 있으면 타임리프는 이 폼이 DTO 객체와 연결되어 있다는 것을 인지함.
  7. 이때 BindingResult 객체가 함께 전달되면 타임리프는 th:objectBindingResult를 연결하여 th:errors와 같은 속성을 통해 오류 메시지를 렌더링하여 출력함.
  • th:object="${...}" 누락.
    • 타임리프 폼(form)에서 BindingResult를 사용하려면 반드시 <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>

2. 참고

profile
Every cloud has a silver lining.

0개의 댓글