ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [김영한 스프링] 21. 검증1 Validation - BindingResult
    Spring/스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 2023. 8. 30. 23:22

    BindingResult1

     

    ValidationItemControllerV2 - addItemV1

        @PostMapping("/add")
        public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
    
            // 검증 로직
            if (!StringUtils.hasText(item.getItemName())) {
                bindingResult.addError(new FieldError("item", "itemName", "상품 이름은 필수입니다."));
            }
    
            if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
                bindingResult.addError(new FieldError("item", "price", "가격은 1,000원 ~ 1,000,000원까지 허용합니다."));
            }
    
            if (item.getQuantity() == null || item.getQuantity() >= 9999) {
                bindingResult.addError(new FieldError("item", "quantity", "수량은 최대 9,999개까지 허용합니다."));
            }
    
            // 특정 필드가 아닌 복합 룰 검증
            if (item.getPrice() != null && item.getQuantity() != null) {
                int resultPrice = item.getPrice() * item.getQuantity();
    
                if (resultPrice < 10000) {
                    bindingResult.addError(new ObjectError("item", "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " +resultPrice));
                }
            }
    
            // 검증에 실패하면 다시 입력 폼으로
            if (bindingResult.hasErrors()) {
                log.info("error = {} ", bindingResult);
    
                return "validation/v2/addForm";
            }
    
            // 성공 로직
            Item savedItem = itemRepository.save(item);
            redirectAttributes.addAttribute("itemId", savedItem.getId());
            redirectAttributes.addAttribute("status", true);
            return "redirect:/validation/v2/items/{itemId}";
        }

    코드 변경

    메서드 이름 변경 : addItem() -> addItemV1()

    @Slf4j : 로그 출력을 위해 추가

     

    ※주의

    item에 바인딩된 결과가 bindingResult에 담기기 때문에 BindingResult bindingResult 파라미터의 위치는 @ModelAttribute Item item 다음에 와야 한다.

     

    FieldError 생성자 요약

    public FieldError(String objectName, String field, String defaultMessage) {}

    필드에 오류가 있으면 FieldError 객체를 생성해서 bindingResult에 담아두면 된다.

    • objectName : @ModelAttribute의 이름
    • field : 오류가 발생한 필드 이름
    • defaultMessage : 오류 기본 메시지

     

    ObjectError 생성자 요약

    public ObjectError(String objectName, String defaultMessage) {}

    특정 필드를 넘어서는 오류가 있으면 ObjectError 객체를 생성해서 bindingResult에 담아두면 된다.

    • objectName : @ModelAttribute의 이름
    • defaultMessage : 오류 기본 메시지

     

     

    validation/v2/addForm.html 수정

            <div th:if="${#fields.hasGlobalErrors()}">
                <p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">글로벌 오류 메시지</p>
            </div>
    
            <div>
                <label for="itemName" th:text="#{label.item.itemName}">상품명</label>
                <input type="text" id="itemName" th:field="*{itemName}"
                       th:errorclass="field-error" class="form-control" placeholder="이름을 입력하세요">
                <div class="field-error" th:errors="*{itemName}">
                    상품명 오류
                </div>
            </div>
            <div>
                <label for="price" th:text="#{label.item.price}">가격</label>
                <input type="text" id="price" th:field="*{price}"
                       th:errorclass="field-error" class="form-control" placeholder="가격을 입력하세요">
                <div class="field-error" th:errors="*{price}">
                    가격 오류
                </div>
            </div>
            <div>
                <label for="quantity" th:text="#{label.item.quantity}">수량</label>
                <input type="text" id="quantity" th:field="*{quantity}"
                       th:errorclass="field-error" class="form-control" placeholder="수량을 입력하세요">
                <div class="field-error" th:errors="*{quantity}">
                    수량 오류
                </div>
            </div>

    global에러가 여러개 있을 수도 있기 때문에 each 사용

     

    타임리프 스프링 검증 오류 통합 기능

    타임리프는 스프링의 BindingResult를 활용해서 편리하게 검증 오류를 표현하는 기능을 제공한다.

    • #fields : #fields로 BindingResult가 제공하는 검증 오류에 접근할 수 있다.
    • th:errors : 해당 필드에 오류가 있는 경우에 태그를 출력한다. th:if의 편의 버전이다.
    • th:errorclass : th:field에서 지정한 필드에 오류가 있으면 class 정보를 추가한

     

     

    실행

     

     

    BindingResult2

     

    스프링이 제공하는 검증 오류를 보관하는 객체이다. 검증 오류가 발생하면 여기에 보관하면 된다.

    BindingResult가 있으면 @ModelAttribute에 데이터 바인딩 시 오류가 발생해도 컨트롤러가 호출된다!

     

    예) @ModelAttribute에 바인딩 시 타입 오류가 발생하면?

    • BindingResult가 없으면 -> 400 오류가 발생하면서 컨트롤러가 호출되지 않고, 오류 페이지로 이동한다.
    • BindingResult가 있으면 -> 오류 정보(FieldError)를 BindingResult에 담아서 컨트롤러를 정상 호출한다.

     

    BindingResult O

    BindingResult가 있으면 문제를 담아서 보여줌

     

     

    BindingResult X

    BindingResult가 없어서 Error Page

     

     

    BindingResult에 검증 오류를 적용하는 3가지 방법

    • @ModelAttribute의 객체에 타입 오류 등으로 바인딩이 실패하는 경우 스프링이 FieldError를 생성해서 BindingResult에 넣어준다.
    • 개발자가 직접 넣어준다.
    • Validator 사용 -> 이것은 뒤에서 설명

     

    타입 오류 확인

    숫자가 입력되어야 할 곳에 문자를 입력해서 타입을 다르게 해서 BindingResult를 호출하고 bindingResult의 값을 확인해 보자.

     

    주의

    • BindingResult는 검증할 대상 바로 다음에 와야한다. 순서가 중요하다. 예를 들어서 @ModelAttribute Item item, 바로 다음에 BindingResult가 와야 한다.
    • BindingResult는 Model에 자동으로 포함된다.

     

    BindingResult와 Errors

    • org.springframework.validation.Errors
    • org.springframework.validation.BindingResult

     

    BindingResult는 인터페이스이고, Errors 인터페이스를 상속받고 있다.

    실제 넘어오는 구현체는 BeanPropertyBindingResult라는 것인데, 둘다 구현하고 있으므로 BindingResult 대신에 Errors를 사용해도 된다. Errors 인터페이스는 단순한 오류 저장과 조회 기능을 제공한다. BindingResult는 여기에 더해서 추가적인 기능들을 제공한다. addError()도 BindingResult가 제공하므로 여기서는 BindingResult를 사용하자. 주로 관례상 BindingResult를 많이 사용한다.

     

    정리

    BindingResult, FieldError, ObjectError를 사용해서 오류 메시지를 처리하는 방법을 알아보았다.

    그런데 오류가 발생하는 경우 고객이 입력한 내용이 모두 사라진다. 이 문제를 해결해 보자.

     

     

    출처 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2

     

    스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

    웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

    www.inflearn.com

Designed by Tistory.