검증 1 - Validation

LeeKyoungChang·2022년 1월 26일
0
post-thumbnail

'스프링 MVC 2편 - 백엔드 웹 개발 활용 기술' 수업을 듣고 정리한 내용입니다.

 

📚 1. 검증 요구사항

상품 관리 시스템에 새로운 요구사항이 추가되었다.

 

요구사항: 검증 로직 추가
(1) 타입 검증

  • 가격, 수량에 문자가 들어가면 검증 오류 처리

(2) 필드 검증

  • 상품명: 필수, 공백X
  • 가격: 1000원 이상, 1백만원 이하
  • 수량: 최대 9999

(3) 특정 필드의 범위를 넘어서는 검증

  • 가격 * 수량의 합은 10,000원 이상

 

🗝 지금까지는...

  • 지금까지 만든 웹 애플리케이션은 폼 입력시 숫자를 문자로 작성하거나해서 검증 오류가 발생하면 오류 화면으로 바로 이동한다.
  • 이렇게 되면 사용자는 처음부터 해당 폼으로 다시 이동해서 입력을 해야 한다.
  • 아마도 이런 서비스라면 사용자는 금방 떠나버릴 것이다. 😱
  • 웹 서비스는 폼 입력시 오류가 발생하면, 고객이 입력한 데이터를 유지한 상태로 어떤 오류가 발생했는지 친절하게 알려주어야 한다.
  • 컨트롤러의 중요한 역할중 하나는 HTTP 요청이 정상인지 검증하는 것이다.

 

💡 참고
클라이언트 검증, 서버 검증

  • 클라이언트 검증은 조작할 수 있으므로 보안에 취약하다.
  • 서버만으로 검증하면, 즉각적인 고객 사용성이 부족해진다.
  • 둘을 적절히 섞어서 사용하되, 최종적으로 서버 검증은 필수이다.
  • API 방식을 사용하면 API 스펙을 잘 정의해서 검증 오류를 API 응답 결과에 잘 남겨주어야 한다.

 

먼저 검증을 직접 구현해보고, 뒤에서 스프링과 타임리프가 제공하는 검증 기능을 활용해보자❗️

 

📚 2. 검증 직접 처리 - 소개

✓ 상품 저장 성공
스크린샷 2022-01-25 오후 4 37 06

  • 사용자가 상품 등록 폼에서 정상 범위의 데이터를 입력하면, 서버에서는 검증 로직이 통과하고, 상품을 저장하고, 상품 상세 화면으로 redirect한다.

 

✓ 상품 저장 검증 실패
스크린샷 2022-01-25 오후 4 39 22

  • 고객이 상품 등록 폼에서 상품명을 입력하지 않거나, 가격, 수량 등이 너무 작거나 커서 검증 범위를 넘어서면, 서버 검증 로직이 실패해야 한다.
  • 이렇게 검증에 실패한 경우 고객에게 다시 상품 등록 폼을 보여주고, 어떤 값을 잘못 입력했는지 친절하게 알려줘야 한다.

 

📚 3. 검증 직접 처리 - 개발

이제 요구사항에 맞추어 검증 로직을 직접 개발해보자❗️

 

📖 A. 상품 등록 검증 - 컨트롤러

ValidationItemControllerV1 - addItem() 수정

   @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}";
    }

 

✓ 검증 오류 보관

Map<String, String> errors = new HashMap<>();

➡️ 만약 검증시 오류가 발생하면 어떤 검증에서 오류가 발생했는지 파악하기 위해 정보를 errors에 저장해둔다!

 

✓ 검증 로직

if (!StringUtils.hasText(item.getItemName())) { 
	errors.put("itemName", "상품 이름은 필수입니다.");
}

➡️ 검증시 오류가 발생하면 errors에 담아둔다.
➡️ 이때 어떤 필드에서 오류가 발생했는지 구분하기 위해 오류가 발생한 필드명을 key 로 사용한다.
➡️ 이후 뷰에서 이 데이터를 사용해서 고객에게 친절한 오류 메시지를 출력할 수 있다.

 

✓ 특정 필드의 범위를 넘어서는 검증 로직

//특정 필드의 범위를 넘어서는 검증 로직
if (item.getPrice() != null && item.getQuantity() != null) {
      int resultPrice = item.getPrice() * item.getQuantity();
      if (resultPrice < 10000) {
		  errors.put("globalError", "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " + resultPrice);
	  } 
}

➡️ 특정 필드를 넘어서는 오류를 처리해야 할 수도 있다.
➡️ 이때는 필드 이름을 넣을 수 없으므로 globalError 라는 key 를 사용한다.

 

✓ 검증에 실패하면 다시 입력 폼으로

if (!errors.isEmpty()) {
      model.addAttribute("errors", errors);
      return "validation/v1/addForm";
}

➡️ 만약 검증에서 오류 메시지가 하나라도 있으면 오류 메시지를 출력하기 위해 modelerrors 를 담고, 입력 폼이 있는 뷰 템플릿으로 보낸다.

 

실행 결과
스크린샷 2022-01-25 오후 4 58 52

스크린샷 2022-01-25 오후 4 59 01

 

📖 B. 상품 등록 검증 - 뷰

addForm.html

이제 오류들을 화면에 출력해주자!

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
          href="../css/bootstrap.min.css" rel="stylesheet">
    <style>
        .container {
            max-width: 560px;
        }
        .field-error {
            border-color: #dc3545;
            color: #dc3545;
        }
    </style>
</head>
<body>
<div class="py-5 text-center">
    <h2 th:text="#{page.addItem}">상품 등록</h2>
</div>

<form action="item.html" th:action th:object="${item}" method="post">

    <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" 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="price" th:text="#{label.item.price}">가격</label>
        <input type="text" id="price" th:field="*{price}"
               th:class="${errors?.containsKey('price')} ? 'form-control field-error': 'form-control'"
               class="form-control" placeholder="가격을 입력하세요">
        <div class="field-error" th:if="${errors?.containsKey('price')}" th:text="${errors['price']}">
            가격 오류
        </div>
    </div>
    <div>
        <label for="quantity" th:text="#{label.item.quantity}">수량</label>
        <input type="text" id="quantity" th:field="*{quantity}"
               th:class="${errors?.containsKey('quantity')} ? 'form-control field-error': 'form-control'"
               class="form-control" placeholder="수량을 입력하세요">
        <div class="field-error" th:if="${errors?.containsKey('quantity')}" th:text="${errors['quantity']}">
            수량 오류
        </div>
    </div>

    <hr class="my-4">

    <div class="row">
        <div class="col">
            <button class="w-100 btn btn-primary btn-lg" type="submit" th:text="#{button.save}">상품 등록</button>
        </div>
        <div class="col">
            <button class="w-100 btn btn-secondary btn-lg"
                    onclick="location.href='items.html'"
                    th:onclick="|location.href='@{/validation/v1/items}'|"
                    type="button" th:text="#{button.cancel}">취소</button>
        </div>
    </div>

</form>
```

 

✓ css 추가

        .field-error {
            border-color: #dc3545;
            color: #dc3545;
        }

➡️ 이 부분은 오류 메시지를 빨간색으로 강조하기 위해 추가했다.

 

✓ 글로벌 오류 메시지

<div th:if="${errors?.containsKey('globalError')}">
	<p class="field-error" th:text="${errors['globalError']}">전체 오류 메시지</p>
</div>

➡️ 오류 메시지는 errors 에 내용이 있을 때만 출력하면 된다.
➡️ 타임리프의 th:if 를 사용하면 조건에 만족할 때만 해당 HTML 태그를 출력할 수 있다.

 

💡 참고
Safe Navigation Operator

  • 만약 여기에서 errorsnull 이라면 어떻게 될까?
  • 생각해보면 등록폼에 진입한 시점에는 errors 가 없다.
  • 따라서 errors.containsKey() 를 호출하는 순간 NullPointerException 이 발생한다.

errors?.

  • errorsnull일 때 NullPointerException이 발생하는 대신, null을 반환하는 문법이다.
  • th:if 에서 null 은 실패로 처리되므로 오류 메시지가 출력되지 않는다.
  • 이것은스프링의 SpringEL이 제공하는 문법이다. 자세한 내용은 사이트를 참고하자!

 

✓ 가격 1000원, 수량 1개를 선택, HTML 결과 화면
스크린샷 2022-01-25 오후 6 02 11

스크린샷 2022-01-25 오후 6 02 24

 

✓ 필드 오류 처리

<input type="text" th:classappend="${errors?.containsKey('itemName')} ? 'field-error' : _"
class="form-control">
스크린샷 2022-01-25 오후 6 05 33

➡️ classappend 를 사용해서 해당 필드에 오류가 있으면 field-error 라는 클래스 정보를 더해서 폼의 색깔을 빨간색으로 강조한다.
➡️ 만약 값이 없으면 _ (No-Operation)을 사용해서 아무것도 하지 않는다.

 

✓ 필드 오류 처리 - 입력 폼 색상 적용

<input type="text" class="form-control field-error">

 

✓ 필드 오류 처리 - 메시지

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

➡️ 글로벌 오류 메시지에서 설명한 내용과 동일하고, 필드 오류를 대상으로 한다.

 

실행

  • http://localhost:8080/validation/v1/items/add
스크린샷 2022-01-25 오후 6 38 57 스크린샷 2022-01-25 오후 6 37 12 스크린샷 2022-01-25 오후 5 41 25 스크린샷 2022-01-25 오후 5 41 30

 

📌 정리

  • 만약 검증 오류가 발생하면 입력 폼을 다시 보여준다.
  • 검증 오류들을 고객에게 친절하게 안내해서 다시 입력할 수 있게 한다.
  • 검증 오류가 발생해도 고객이 입력한 데이터가 유지된다.
  • 상품 수정의 검증은 더 효율적인 검증 처리 방법을 학습한 다음에 진행한다.

 

🔊 남은 문제점

  • 뷰 템플릿에서 중복 처리가 많다.
  • 타입 오류 처리가 안된다.
    • Itemprice , quantity 같은 숫자 필드는 타입이 Integer 이므로 문자 타입으로 설정하는 것이 불가능하다.
      ➡️ 숫자 타입에 문자가 들어오면 오류가 발생한다.
    • 이러한 오류는 스프링MVC에서 컨트롤러에 진입하기도 전에 예외가 발생하기 때문에
      ➡️ 컨트롤러가 호출되지도 않고, 400 예외가 발생하면서 오류 페이지를 띄워준다.
    • 고객은 본인이 어떤 내용을 입력해서 오류가 발생했는지 이해하기 어렵다.
      ➡️ 결국 고객이 입력한 값도 어딘가에 별도로 관리가 되어야 한다.

 

이제 스프링이 제공하는 검증 방법을 하나씩 알아보자!

 

📚 4. 프로젝트 준비 V2

앞서 만든 기능을 유지하기 위해, 컨트롤러와 템플릿 파일을 복사하자!
수업자료 참고

 

결과
스크린샷 2022-01-26 오전 10 25 11

 

📚 5. BindingResult1

  • 지금부터 스프링이 제공하는 검증 오류 처리 방법을 알아보자.
  • 여기서 핵심은 BindingResult 이다.

 

ValidationItemControllerV2 - addItemV1

      @PostMapping("/add")
    public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {

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

        // 검증 로직
        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("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}";
    }

 

✓ BindingResult bindingResult 파라미터의 위치

public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {}

➡️ BindingResult bindingResult 파라미터의 위치는 @ModelAttribute Item item 다음에 와야 한다.

 

✓ 필드 오류 - FieldError

if (!StringUtils.hasText(item.getItemName())) { 
	bindingResult.addError(new FieldError("item", "itemName", "상품 이름은  필수입니다.")); 
}

➡️ FieldError 생성자 요약

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

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

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

 

✓ 글로벌 오류 - ObjectError

bindingResult.addError(new ObjectError("item","가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " + resultPrice));

➡️ ObjectError 생성자 요약

public ObjectError(String objectName, String defaultMessage) {}

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

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

 

validation/v2/addForm.html 수정


    <form action="item.html" th:action th:object="${item}" method="post">

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

 

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

 

✓ 글로벌 오류 처리

<div th:if="${#fields.hasGlobalErrors()}">
      <p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="$
{err}">전체 오류 메시지</p> </div>

 

✓ 필드 오류 처리

<input type="text" id="itemName" th:field="*{itemName}" th:errorclass="field-error" class="form-control" placeholder="이름을 입력하세요">
<div class="field-error" th:errors="*{itemName}">
	상품명 오류
</div>

 

실행
스크린샷 2022-01-26 오전 11 29 51

스크린샷 2022-01-26 오전 11 30 00 스크린샷 2022-01-26 오전 11 33 42

 

📚 6. BindingResult2

  • 스프링이 제공하는 검증 오류를 보관하는 객체이다. 검증 오류가 발생하면 여기에 보관하면 된다.
  • BindingResult 가 있으면 @ModelAttribute 에 데이터 바인딩 시 오류가 발생해도 컨트롤러가
    호출된다!

 

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

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

 

✓ BindingResult에 검증 오류를 적용하는 3가지 방법
(1) @ModelAttribute 의 객체에 타입 오류 등으로 바인딩이 실패하는 경우 스프링이 FieldError 생성해서 BindingResult 에 넣어준다.
(2) 개발자가 직접 넣어준다.
(3) Validator 사용 → 이것은 뒤에서 설명

 

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

스크린샷 2022-01-26 오전 11 38 00

 

⚠️ 주의

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

 

📖 A. BindingResult와 Errors

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

 

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

 

📌 정리

  • BindingResult , FieldError , ObjectError 를 사용해서 오류 메시지를 처리하는 방법을 알아보았다.
  • 그런데 오류가 발생하는 경우 고객이 입력한 내용이 모두 사라진다. 이 문제를 해결해보자❗️

 

📚 7. FieldError, ObjectError

🔔 목표

  • 사용자 입력 오류 메시지가 화면에 남도록 하자.
  • FieldError, ObjectError 에 대해서 더 자세히 알아보자.

 

ValidationItemControllerV2 - addItemV2

@PostMapping("/add")
    public String addItemV2(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {

        // 검증 로직
        if (!StringUtils.hasText(item.getItemName())) {
            bindingResult.addError(new FieldError("item", "itemName", item.getItemName(), false, null, null, "상품 이름은 필수 입니다."));
        }
        if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
            bindingResult.addError(new FieldError("item", "price", item.getPrice(), false, null, null, "가격은 1,000 ~ 1,000,000 까지 허용합니다."));
        }
        if (item.getQuantity() == null || item.getQuantity() >= 9999) {
            bindingResult.addError(new FieldError("item", "quantity", item.getQuantity(), false, null, null, "수량은 최대 9,999 까지 허용합니다."));
        }

        // 특정 필드가 아닌 복합 룰 검증 (특정 필드의 범위를 넘어서는 검증 로직)
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                bindingResult.addError(new ObjectError("item", null, null, "가격 * 수량의 합은 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}";
    }

 

FieldError 생성자

public FieldError(String objectName, String field, String defaultMessage);
public FieldError(String objectName, String field, @Nullable Object rejectedValue, boolean bindingFailure, @Nullable String[] codes, @Nullable Object[] arguments, @Nullable String defaultMessage)

❗️파라미터 목록 ❗️

  • objectName : 오류가 발생한 객체 이름
  • field : 오류 필드
  • rejectedValue : 사용자가 입력한 값(거절된 값)
  • bindingFailure : 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값
  • codes : 메시지 코드
  • arguments : 메시지에서 사용하는 인자
  • defaultMessage : 기본 오류 메시지

(ObjectError 도 유사하게 두 가지 생성자를 제공한다.)

 

📖 A. 오류 발생시 사용자 입력 값 유지

new FieldError("item", "price", item.getPrice(), false, null, null, "가격은 1,000 ~ 1,000,000 까지 허용합니다.")

➡️ 사용자의 입력 데이터가 컨트롤러의 @ModelAttribute 에 바인딩되는 시점에 오류가 발생하면 모델 객체에 사용자 입력 값을 유지하기 어렵다.
➡️ 예를 들어서 가격에 숫자가 아닌 문자가 입력된다면 가격은 Integer 타입이므로 문자를 보관할 수 있는 방법이 없다.
➡️ 오류가 발생한 경우 사용자 입력 값을 보관하는 별도의 방법이 필요하다.
➡️ 이렇게 보관한 사용자 입력 값을 검증 오류 발생시 화면에 다시 출력하면 된다.

 

  • FieldError : 오류 발생시 사용자 입력 값을 저장하는 기능을 제공한다.
    • rejectedValue : 오류 발생시 사용자 입력 값을 저장하는 필드이다.
  • bindingFailure : 타입 오류 같은 바인딩이 실패했는지 여부를 적어주면 된다. 바인딩이 실패한 것은 아니기 때문에 false 를 사용한다.

 

✓ 타임리프의 사용자 입력 값 유지
th:field="*{price}"
th:field 동작 방식

  • 정상 상황 : 모델 객체의 값을 사용
  • 오류 발생 : FieldError 에서 보관한 값을 사용해서 값을 출력

 

✓ 스프링의 바인딩 오류 처리

  • 타입 오류로 바인딩에 실패하면 스프링은 FieldError 를 생성하면서 사용자가 입력한 값을 넣어둔다.
  • 그리고 해당 오류를 BindingResult 에 담아서 컨트롤러를 호출한다.
    ➡️ 따라서, 타입 오류 같은 바인딩 실패시에도 사용자의 오류 메시지를 정상 출력할 수 있다.

실행 결과
스크린샷 2022-01-26 오후 12 44 46

 


참고

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글