V2.1. Binding Result 적용 하기

알파로그·2023년 3월 26일
0

Spring MVC 활용 기술

목록 보기
32/42

🔗 요구사항 확인

✏️ Binding Result 적용 - V1.

📍 Controller 계층

  • BindingResult
    • Spring 에서 제공하는 validation 라이브러리
    • Map 의 기능을 BindingResult 이 대신할 수 있으므로 Map 은 삭제한다.
    • 검증을 통과하지 못할경우 BindingResulterror 를 추가해준다.
    • Model 로 넘겨주지 않아도 Spring 이 전달해준다.
  • FieldError
    • 특정 필드에서 에러가 발생할경우
    • Param 1 : Post 로 등록할 객체명을 적어주면 된다.
    • Param 2 : 오류 코드를 적어주면 된다.
    • Param 3 : 클라이언트 메시지를 적어주면 된다.
  • ObjectError
    • 특정 필드가 아닌 복합 룰에 의해서 에러가 발생할경우
    • Param 1 : Post 로 등록할 객체명을 적어주면 된다.
    • Param 2 : 클라이언트 메시지를 적어주면 된다.
  • ⚠️ BindingResult 의 파라미터 위치는 항상 바인딩의 대상이 되는 객체 바로 뒤에 위치해야 한다.
    • 예제에서는 Item 이 바인딩의 대상이므로
      @ModelAttribute Item item 바로 뒤에 위치시켜야 한다.
    • 바로 뒤에 위치시키지 않아도 뒤에만 있다면 작동은 되지만,
      모든 기능 (type 검증 등)이 제대로 작동되지 않는다.
@PostMapping("/add")
public String addItemV1(
        @ModelAttribute Item item,
        BindingResult bindingResult,
        RedirectAttributes redirectAttributes
) {
    
    // 특정 필드 검증 로직
    if (!StringUtils.hasText(item.getItemName()))
        bindingResult.addError(new FieldError(
                "item",
                "itemName", 
                "상품 이름은 필수입니다.")
        );

    if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 100000)
        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("errors = {}", 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}";
}

📍 web 계층 - global 에러 처리

  • if 문을 사용해 글로벌 오류가 있다면 오류메시지를 렌더링하는 로직
    • 글로벌 오류가 여러가지가 있을 수 있으므로 each 문을 사용한다.
  • #fields
    • BindingResult 의 Thymeleaf 변수 표현식
    • 모델로 넘어온 BindingResult 를 사용할 수 있다.
<!-- 글로벌 오류 표시 시작-->
<div th:if="${#fields.hasGlobalErrors()}">

    <p class="field-error"
       th:each="err : ${#fields.globalErrors()}"
       th:text="${err}"></p>

</div>
<!-- 글로벌 오류 표시 종료-->

📍 Field 에러 처리

  • th:errorclass
    • BindingResult 에 필드 에러가 있을경우 실행된다.
      • th:field 에 정의된 변수를 기반으로 해당 필드가 어떤필드인지 판단한다.
    • class append 처럼 참일경우 기존 class 속성에 th:errorclass 에 정의된 속성이 추가된다.
  • th:errors
    • BindingResult 에 필드 에러가 있을경우 에러메시지가 text 로 출력된다.
      • BindingResult 의 매개변수인 object name 과 field 를 기반으로 처리됨
    • 상위 테그에 th:object 로 선언했다면 예제처럼 선택변수를 사용할 수 있다.
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<input type="text"
       th:field="*{itemName}"
       th:errorclass="field-error" class="form-control"
       placeholder="이름을 입력하세요">
<div class="field-error"
     th:errors="*{itemName}">
</div>

📍 나머지 코드도 변경

<!-- 글로벌 -->
<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"
           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"
           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"
           th:field="*{quantity}"
           th:errorclass="field-error" class="form-control"
           placeholder="수량을 입력하세요">
    <div class="field-error"
         th:errors="*{quantity}">
    </div>
</div>

✏️ 문제점

  • 필드 에러가 발생할경우 클라이언트가 입력한 data 가 유지되지 않는다.
profile
잘못된 내용 PR 환영

0개의 댓글