Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

검증 직접 처리, validation 본문

인프런/[인프런] 스프링 MVC 2

검증 직접 처리, validation

Seung__ 2021. 9. 24. 16:02

1. 상품 저장 성공 시나리오


 

 

2. 상품 저장 실패 시나리오


  • 이런 저런 이유로 data가 누락되거나 잘못된 data가 넘어오는 경우 검증에 실패해야함.
  • 이렇게 검증에 실패한 경우 고객에게 다시 상품 등록 폼을보여주고, 
  • 어떤 값을 잘못 입력했는지 알려줘야함.
  • 검증에 실패할 경우 Model에 data를 담아서 상품등록 폼에 다시 전달함.
  • Thymeleaf는 넘어온 data를 가지고 랜더링을 다시함.

 

3. 검증로직 추가 : addItem.


  • 검증시 오류가 발생하면 errors 에 담아둔다. 
  • 이때 어떤 필드에서 오류가 발생했는지 구분하기 위해 오류가 발생한 필드명을 key 로 사용한다. 
  • 이후 뷰에서 이 데이터를 사용해서 오류 메시지를 출력
    @PostMapping("/add")
    public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes, Model model) {

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

        //검증 로직
        if(!StringUtils.hasText(item.getItemName())){
            errors.put("itemName", "상품 이름을 필수 입니다.");
        }
        if(item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000){
            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()){
            log.info("errors = {}", errors);
            model.addAttribute("errors", errors);
            return "validation/v1/addForm";
        }

        //성공 로직.
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/validation/v1/items/{itemId}";
    }
  • 확인해보자.
    • 상품 명은 비우고, 가격을 1000원 밑으로 해보자.

  • 검증이 의도한대로 잘 동작한다.
errors = {itemName=상품 이름을 필수 입니다., price=가격은 1,000 ~ 1,000,000까지 허용합니당.}
  • 어 근데? 검증 logic fail시 입력된 정보를 따로 model에 담은 적이없는데??
    • @PostMapping("/add")
          public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes, Model model)
    • @ModelAttribute Item item은 model.addAttribute("item", item) 기능도 제공한다.
    • 따라서 등록 폼에서 작성한 내용이 controller로 넘어오고 model에 담기기 때문에
    • 검증 fail시 해당 값을 재사용가능

 

 

4. addForm.html 수정


  • globalError 발생 시 view에서 나타낼 수 있도록 html  수정
    • errors?.containsKey('globalError')
    • errors? -> errors가 null이면 무시하는 로직.
    • Map사용하기 > erros['key']
        <div th:if="${errors?.containsKey('globalError')}">
            <p class="field-error" th:text="${errors['globalError']}">global error message</p>
        </div>

  • 랜더링 결과
        <div>
            <p class="field-error">가격 * 수량은 10,000원 이상이어야 합니다. 현재 값 = 10</p>
        </div>

 

  • validation fail 시 테두리도 빨간색으로.
    <style>
        .container {
            max-width: 560px;
        }
        .field-error {
            border-color: #dc3545;
            color: #dc3545;
        }
    </style>
  • th:class, 삼항 연산자를 활용
  • error가 있으면 input box의 클래스를 form-control, field-error로 지정.
  • error가 없으면 form-control로 지정.
        <div>
            <label for="itemName" th:text="#{label.item.itemName}">상품명</label>
            <input type="text" id="itemName" th:field="*{itemName}"
                   th:class="${errors?.containsKey('itemName')} ? 'form-control field-error' : 'form-control'"
                   class="form-control" placeholder="이름을 입력하세요">
            <div class="field-error" th:if="${errors?.containsKey('itemName')}" th:text="${errors['itemName']}">
                상품명 오류
            </div>
        </div>

  • 랜더링 결과
        <div>
            <label for="itemName">상품명</label>
            <input type="text" id="itemName" class="form-control field-error" placeholder="이름을 입력하세요" name="itemName" value="">
            <div class="field-error">상품 이름을 필수 입니다.</div>
        </div>
  • 나머지 항목도 동일하게 적용

 

 

5. Safe Navigation Operator


  • 만약 여기에서 errors 가 null 이라면 어떻게 될까?
  • 생각해보면 등록폼에 진입한 시점에는 errors 가 없다.
  • 하지만 addForm.html에서는 error는 사용한다.
  • 따라서 errors.containsKey() 를 호출하는 순간 NullPointerException 이 발생한다.
  • errors?. 은 errors 가 null 일때 NullPointerException 이 발생하는 대신, null 을 반환.
  • th:if 에서 null 은 실패로 처리되므로 오류 메시지가 출력되지 않는다.
    이것은 스프링의 SpringEL이 제공하는 문법이다. 자세한 내용은 다음을 참고
 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

 

6. 필드 오류 처리, classAppend


  • errors가 있으면 class에 field-error를 추가하고 
  • 없으면 '_'(no-operation)을 사용해서 아무것도 하지 않음 
<input type="text" th:classappend="${errors?.containsKey('itemName')} ? 'field-error' : _"
	class="form-control">

 

 

7. 정리


  • 만약 검증 오류가 발생하면 입력 폼을 다시 보여준다.
  • 검증 오류들을 고객에게 친절하게 안내해서 다시 입력할 수 있게 한다.
  • 검증 오류가 발생해도 고객이 입력한 데이터가 유지된다.

 

 

8. 남은 문제점


  • 뷰 템플릿에서 중복 처리가 많다. 뭔가 비슷하다.
  • 타입 오류 처리가 안된다. 
    • Item 의 price , quantity 같은 숫자 필드는 타입이 Integer 이므로 문자 타입으로 설정하는 것이 불가능하다. 
    • 숫자 타입에 문자가 들어오면 오류가 발생한다. 
    • 그런데 이러한 오류는 스프링MVC에서 컨트롤러에 진입하기도 전에 예외가 발생하기 때문에, 
    • 컨트롤러가 호출되지도 않고, 400 예외가 발생하면서 오류 페이지를 띄워준다.
    • Item 의 price 에 문자를 입력하는 것 처럼 타입 오류가 발생해도 
    • 고객이 입력한 문자를 화면에 남겨야 한다. 
    • 만약 컨트롤러가 호출된다고 가정해도 Item 의 price 는 Integer 이므로 문자를 보관할 수가 없다.
    • 결국 문자는 바인딩이 불가능하므로 고객이 입력한 문자가 사라지게 되고, 
    • 고객은 본인이 어떤 내용을 입력해서 오류가 발생했는지 이해하기 어렵다.
    • 결국 고객이 입력한 값도 어딘가에 별도로 관리가 되어야 한다.

 

9. GitHub : Validation-V1


 

GitHub - bsh6463/Validation-V1

Contribute to bsh6463/Validation-V1 development by creating an account on GitHub.

github.com

 

'인프런 > [인프런] 스프링 MVC 2' 카테고리의 다른 글

BindingResult2  (0) 2021.09.24
BindingResult1  (0) 2021.09.24
검증 요구사항.  (0) 2021.09.24
WebApplication 국제화 적용  (0) 2021.09.24
WebApplication에 메시지 적용  (0) 2021.09.24
Comments