이전 Validation V1에서는 입력 폼의 서버 검증 방법으로 if조건에서 error를 Map타입 객체에 담아서 Model로 프론트에 문구를 보여준 방식이였다. 이번에는 스프링에서 제공하는 BindingResult를 사용하는 방법을 소개하려고합니다.
@PostMapping("/add")
public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes , Model model) {
//검증 로직
if(!StringUtils.hasText(item.getItemName())) { // 상품이름이 없으면
bindingResult.addError(new FieldError("item","itemName","상품이름은 필수입니다."));
}
if(item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000){ //상품가격이 1000에서 1000000사이가 아닐경우
bindingResult.addError(new FieldError("item","price","가격은 1,000에서 1,000,000까지 허용합니다."));
}
if(item.getQuantity()==null|| item.getQuantity() >= 9999) { //수량이 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()){
return "validation/v2/addForm";
}
//성공 로직
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v2/items/{itemId}";
}
해당 컨트롤러의 매개변수를 보면 ModelAttribute 다음에 BindingResult를 받은 모습인데 순서가 중요하다.
반드시 BindingResult는 ModelAttribute 다음에 와야한다.
위와 같은 입력 폼에서 받아온 값을 if문 조건에 따라 bindingResult에 addError를 사용하여 담은 코드이다.
addError를 받는 모습을 보면 new FieldError를 사용하였는데 ModelAttribute에 담긴 필드(속성)을 통해 담을 것이기 때문에 가능한 코드이다. 해당 코드에는 item의 itemName, price, quantity 3개의 속성을 통해 담았다.
new FiedError의 인자 순서는 objectName, field, defaultMessage가 된다.
objectName은 ModelAttirbute의 타입이고, field는 속성이름, defaultMessage는 오류 메세지를 작성하면 된다.
마지막 조건절에는 new ObjectError가 보이는데 해당 오류는 필드에 잇는 값을 통해 저장하는게 아니므로 기본적인 objectName과 defaultMassage를 담아간다.
그리고 모든 if조건문을 실행하고 bindingResult의 error가 담겨잇다면 에러를 가지고 다시 폼으로 이동한다.
<div class="container">
<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="${#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>
<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/v2/items}'|"
type="button" th:text="#{button.cancel}">취소</button>
</div>
</div>
</form>
</div> <!-- /container -->
에러를 가지고 온 html파일이다. 여기선 기존의 item에 field를 호출하는 타입스크립트인 뿐인데 th:errors를 사용하여 해당 필드에 오류가 담겨있을 경우에 따라 클래스를 추가한다던지 태그를 보이게 할 수 있다.
th:errorclass="field-error" -> 해당 필드에 에러가 담겨왔다면 class추가
th:errors="*{quantity}" -> 지정한 필드에 에러가 담겨왔다면 해당 태그 노출
이런 방식으로 스프링에선 보단 간결하게 객체에 에러를 담아서 추가적인 호출 없이 기존의 필드호출만으로 에러를 구분하여 보여줄 수 있다.
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">글로벌 오류 메시지</p>
</div>
상단에 globalError를 체크하는 코드가 잇는데 컨트롤러 마지막 조건절에 new Object로 담은 에러가 담겨있다.
해당 globalError는 특정 필드에 속한게 아니므로 에러 메세지가 들어올수 있게 되어 th:each를 사용하여 순서대로 표기한다.
여기까지가 Validation V2에 대한 설명이다. 여기까지의 V2에 문제는 아직 많이 존재한다. 에러 메세지를 공통으로 사용할 수 없어서 비슷한 문구를 모든곳에 찾아가 수정을 해야한다던지, 저장 버튼을 눌렀을때 기존의 값을 유지하지 못하고 빈 폼이 된다던지..
다음 글에서는 값을 유지하는 방법을 작성할 것이다.
'spring' 카테고리의 다른 글
Validation 분리 (0) | 2024.05.15 |
---|---|
Validation V5 (0) | 2024.05.15 |
Validation V4 (1) | 2024.05.14 |
Validation V3 (0) | 2024.05.14 |
Validation V1 (2) | 2024.05.12 |