V1. 직접 검증 처리하기

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

Spring MVC 활용 기술

목록 보기
31/42
post-custom-banner

✏️ 검증 로직 구현

🔗 검증 요구사항

📍 Controller - 상품 등록 처리 method

  • 검증 요구사항에 있었던 조건을 if 문을 사용해 controller 에 구현했다.
    • Map 을 사용해 key 는 오류 코드 , value 는 클라이언트에게 보여줄 안내 메시지를 적는다.
  • 이렇게 까지만 구현하고 실행해보면 조건을 만족하지 못한 등록은 다시 폼으로 이동시킨다.
    - 🔗 입력 폼 처리
    - 폼을 요청하는 GetMapping 메서드에서 new Item 객체를 생성했기 때문에 등록 폼으로 이동할 때 사용자가 입력했던 값이 지워지지 않고 남아있게 된다.
    - 폼에서 Param 으로 받아온 Item 객체를 그대로 다시 Model 에 전달하고,
    field 가 그 item 으로 value 를 만들어주기 때문
    🔗 RedirectAttributes
@PostMapping("/add")
public String addItem(
        @ModelAttribute Item item,
        RedirectAttributes redirectAttributes,
        Model model
) {

    //-- 검증 로직 --//
    // map 으로 검증 오류 결과를 보관
    Map<String, String> errors = new HashMap<>();

    // 특정 필드 검증 로직
    if (!StringUtils.hasText(item.getItemName()))
        errors.put("itemName", "상품 이름은 필수입니다.");

    if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 100000)
        errors.put("price", "가격은 1,000 ~ 1,000,000 까지 허용합니다.");

    if (item.getQuantity() == null || item.getQuantity() >= 9999)
        errors.put("quantity", "수량은 최대 9,999 까지 허용됩니다.");

    // 특정 필드가 아닌 복합 룰 검증
    if (item.getPrice() != null && item.getQuantity() != null) {
        int resultPrice = item.getPrice() * item.getQuantity();
        if (resultPrice < 10000)
            errors.put("globalError", "가격 * 수량의 합은 10,000 원 이상이여야 합니다. 현재 값 = " + resultPrice);
    }

    //-- 검증 실패 로직 --//
    if (!errors.isEmpty()) {
        model.addAttribute("errors", errors);
        // 검증 실패시 다시 form 으로 이동
        return "validation/v1/addForm";
    }

    //-- 검증 성공 로직 --//
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/validation/v1/items/{itemId}";
}

⚠️ 검증 로직이 잘 작동되고있는지 더 자세히 확인하고 싶으면 로그를 사용하면 된다.


📍 Web 계층 - 복합 룰 에러 메시지

  • 조건문을 사용해 errors 에 globalError 가 있는지 확인하고,
    있다면 text 속성으로 errors 의 value 를 나타나게 구현
<!-- css -->
.field-error {
    border-color: #dc3545;
    color: #dc3545;
}

<!-- 글로벌 오류 표시 시작-->
<div th:if="${errors?.containsKey('globalError')}">
    <p class="field-error"
       th:text="${errors['globalError']}"></p>
</div>
<!-- 글로벌 오류 표시 종료-->

📍 item name 검증

  • intput
    • th:class 속성에 조건식을 걸어 검증에 실패할경우 CSS 가 달라지도록 설정했다.
    • 🔗 Thymeleaf 조건식
  • 에러 메시지
    • 조건문을 사용해 erros 의 key 가 있다면 value 를 랜더링 하게 구현
    • 🔗 Thymeleaf 조건문 (조건문과 조건식은 다르다)
  • ⚠️ errors 뒤에 ? 를 붙여줘야하는 이유
    • 신규로 등록하는 상황은 map 을 model 로 보내지 못했기 때문에 errors 자체가 null 이 된다.
    • errors.containsKey 은 null 에 .containsKey 을 했다는 의미가 되므로 500 에러가 발생하게된다.
    • ? 은 해당 변수가 null 일때 NPE 를 발생시키지 않고 null 을 반환시켜주는 표현식이다.
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>

<input type="text"
       th:field="*{itemName}"
       th:class="${errors?.containsKey('itemName')} ? 'form-control field-error' : 'form-control'"
       placeholder="이름을 입력하세요">

<div class="field-error"
     th:if="${errors?.containsKey('itemName')}">
    [[${errors['itemName']}]]
</div>

⚠️ Class append 를 사용하는 방법

🔗 속성값 설정

  • 완전히 동일한 기능을 하지만 th:classappend 를 사용하는 방법도 있다.
    • class appaend 에 조건을 걸어서 참일경우에 기본 html class 에 속성을 더해주는 방법이다.
    • 개인적으로 정적 html 속성은 필요하지 않다면 쓰고싶지 않아서 끌리는 방법은 아닌것 같다.
<input type="text" 
       th:classappend="${errors?.containsKey('itemName')} ? 'field-error' : _"
       class="form-control">

📍 나머지 항목도 작업

  • 나머지도 똑같이 만들어주면 된다.
    • 완성된 모습
<div th:if="${errors?.containsKey('globalError')}">
    <p class="field-error"
       th:text="${errors['globalError']}"></p>
</div>

<div>
    <label for="itemName" th:text="#{label.item.itemName}">상품명</label>
    <input type="text"
           th:field="*{itemName}"
           th:class="${errors?.containsKey('itemName')} ? 'form-control field-error' : 'form-control'"
           placeholder="이름을 입력하세요">
    <div class="field-error"
         th:if="${errors?.containsKey('itemName')}">
        [[${errors['itemName']}]]
    </div>
</div>
<div>
    <label for="price" th:text="#{label.item.price}">가격</label>
    <input type="text"
           th:field="*{price}"
           th:class="${errors?.containsKey('price')} ? 'form-control field-error' : 'form-control'"
           placeholder="가격을 입력하세요">
    <div class="field-error"
         th:if="${errors?.containsKey('price')}">
        [[${errors['price']}]]
    </div>
</div>
<div>
    <label for="quantity" th:text="#{label.item.quantity}">수량</label>
    <input type="text"
           th:field="*{quantity}"
           th:class="${errors?.containsKey('quantity')} ? 'form-control field-error' : 'form-control'"
           placeholder="수량을 입력하세요">
    <div class="field-error"
         th:if="${errors?.containsKey('quantity')}">
        [[${errors['quantity']}]]
    </div>
</div>

✏️ V1. 방식의 문제점

  • 중복이 너무 많다.
    • 하나의 input 로직에 같은 변수를 계속해서 입력해주어야 한다.
  • type 오류를 해결할 수 없다.
    • input 박스에 타입이 다른 text 를 입력할경우 예외처리가 되지 않고 화이트라벨로 이동시킨다.
    • Post method 가호출되지 않기때문에 클라이언트가 작성한 data 보존을 보장할 수 없다.
    • 고객은 어떤 문제로 오류가 발생했는지 확인하기 힘들다.
profile
잘못된 내용 PR 환영
post-custom-banner

0개의 댓글