Spring MVC Part 2 - 타임리프 - Validation

bin1225·2021년 12월 10일
0

Spring

목록 보기
10/15
post-thumbnail

검증

  • 일반적인 정보 저장 흐름

  • 검증에 실패했을 때

    post 요청을 받았을 때 받은 데이터를 검증하고, 문제가 있다면 미리 설정해둔 오류 결과를 model에 담아서 다시 상품 등록 폼으로 전달한다.
    상품 등록 폼에서는 model에 오류메세지가 담겨 있는지 확인하고 만약 그렇다면 오류 내용을 화면에 출력하여 고객에게 알려준다.

  • 타임리프를 사용하지 않았을 때 검증 과정

//특정 필드가 아닌 복합 룰 검증
 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);
 return "validation/v1/addForm";
 }
 
 //성공 로직
 Item savedItem = itemRepository.save(item);
 redirectAttributes.addAttribute("itemId", savedItem.getId());
 redirectAttributes.addAttribute("status", true);
 return "redirect:/validation/v1/items/{itemId}";

이런식으로 직접 error 정보를 담는 map을 선언하고 error 메세지를 담는다.
error가 비워져 있지 않다면 오류가 발생한 것이므로 다시 등록폼으로 이동하고, 아니라면 상품상세로 이동하도록 한다.

  • view

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

이렇게 thymeleaf 의 if문을 통해서 error메세지 여부를 확인하고 view 에서 화면에 출력한다.

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

errors?. 은 errors 가 null 일때 NullPointerException 이 발생하는 대신, null 을 반환하는 문법이다.
th:if 에서 null 은 실패로 처리되므로 오류 메시지가 출력되지 않는다

BindingResult

BindingResult는 스프링이 제공하는 검증 오류 처리 방법의 핵심 기능이다.

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

주의사항으로는 BindingResult 는 검증할 대상 바로 다음에 와야한다. 순서가 중요하다. 예를 들어서 @ModelAttribute Item item , 바로 다음에 BindingResult 가 와야 한다.

BindingResult를 사용하면 기존에 map을 이용해 오류를 검증했던 코드와 비교했을 때 훨씬 더 간결하고 편리하게 코드를 작성할 수 있다.

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

ObjectError 생성자 요약
public ObjectError(String objectName, String defaultMessage) {}
  • view
<div th:if="${#fields.hasGlobalErrors()}">
 <p class="field-error" th:each="err : ${#fields.globalErrors()}"
th:text="${err}">글로벌 오류 메시지</p>
 </div>

이렇게 #fields를 통해 자동으로 model에 담긴 검증결과에 접근할 수 있다.

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

또 기존의 th:if / th:text 의 코드를 th:errors 를 통해서 간결하게 코드를 작성한다. th:errors를 사용하면 자동으로 그 에러가 발생했는지 확인하고 설정해둔 메세지를 출력한다.

th:errorclass는 해당 field에 대한 검증오류를 확인하고 만약 오류가 존재한다면 해당 클래스에 errorclass를 덧붙여서 렌더링 한다.

bindingResult는 자동으로 타입오류도 검증해준다. 값을 받는 과정에서 서버에 넘어온 타입이 입력돼야 하는 타입과 다르다면 자동으로 오류를 발생시켜 bindingResult에 저장한다.

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

두 번째 생성자를 이용하면 rejectedValue 값을 입력할 수 있고, 그러면 만약 오류가 발생해서 해당 페이지를 다시 전송하더라도 사용자가 입력한 값이 유지되게 할 수 있다.

파라미터 목록

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

0개의 댓글