-
Spring MVC - 스프링에서 제공하는 검증(Validation) 방법 (FieldError, ObjectError)Spring/Spring MVC 2022. 2. 5. 23:21728x90반응형
스프링에서 제공하는 검증(Validation) 방법
BindingResult
- 스프링에서 제공하는 검증 방법의 핵심
- 검증 오류를 보관하는 객체, 검증 오류가 발생 시 BindingResult에 보관
- Model에 자동으로 저장해준다.
- 바인딩시 데이터 타입 오류가 발생해도 컨트롤러를 호출해 준다.
- BindingResult 미사용 → 400 오류 페이지로 이동된다.
- BindingResult 사용 → 오류 정보(FieldError)를 BindingResult에 담아서 컨트롤러 호출 - BindingResult에 검증 오류를 적용하는 3가지 방법
1. @ModelAttribute의 객체에 타입 오류 등으로 바인딩이 실패하는 경우 스프링이
FieldError를 생성하여 BindingResult에 넣어준다.
2. 개발자가 직접 addError 메서드를 통해 넣어준다.
3. Validator 사용
BindingResult와 Errors
BindingResult는 Errors 인터페이스를 상속받은 인터페이스다. 실제로 사용하는 구현체는 BeanPropertyBindingResult 클래스이므로 BindingResult와 Errors 모두 사용해도 되지만 Errors 인터페이스는 단순한 오류 저장과 조회 기능만 제공하고, BindingResult는 추가적인 기능을 제공한다(addError 메서드 등). 따라서 주로 BindingResult를 많이 사용한다.
검증 기능 구현(Spirng에서 제공하는 검증 기능 사용)
Controller에서 검증 기능 구현
다음과 같은 검증 기능을 구현해보자.
- 타입 검증
- 가격, 수량에 문자가 들어가면 검증 오류 처리 - 필드 검증
- 상품명: 필수, 공백 X
- 가격: 1000원 이상, 1백만원 이하
- 수량: 최대 9999 - 특정 필드의 범위를 넘어서는 검증
- 가격 * 수량의 합은 10,000원 이상
- BindingResult는 @ModelAttribute 객체 다음에 와야 한다.
public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
- 필드가 검증에 위반되는 경우 BindingResult에 FiledError 객체를 담아준다.
▶ 필드 오류 : FieldErrorif(!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", "price", "수량은 최대 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)); } }
- public FieldError(String objectName, String field, String defaultMessage) {}
objectName : @ModelAttribute 이름
field : 오류가 발생한 필드 이름
defaultMessage : 기본 오류 메시지
▶ 글로벌 오류 : ObjectError
- public ObjectError(String objectName, String defaultMessage) {}
objectName : @ModelAttribute 이름
defaultMessage : 기본 오류 메시지 - 검증에 위반된 것이 하나라도 있을 시 입력 폼으로
▶ hasErrors - errors가 있으면 true, 없으면 false 반환if (bindingResult.hasErrors()) { log.info("errors={}", bindingResult); return "validation/v2/addForm"; }
- 전체 코드
@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) { bindingResult.addError(new FieldError("item", "price", "가격은 1,000 ~ 1,000,000 까지 허용합니다.")); } if (item.getQuantity() == null || item.getQuantity() >= 9999) { bindingResult.addError(new FieldError("item", "price", "수량은 최대 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}"; }
검증 위반에 따른 errors 메시지 출력
서버단에서 검증 구현을 하였다. 이제 검증에 위반하였을 때 화면에 해당 검증 위반 메시지가 화면에 나타나도록 HTML을 수정해보자.
- 위에서 구현한 검증 중 글로벌 에러(ObjectError)가 있을 시 화면에 에러 메시지 출력
서버를 가동시켜 웹브라우저에서 HTML의 소스 코드를 보면 다음과 같다.<div th:if="${#fields.hasGlobalErrors()}"> <p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">글로벌 오류 메시지</p> </div>
▶ #fields : BindingResult에 저장된 errors에 접근할 수 있다.<div> <p class="field-error">가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재값 = 1</p> </div>
- #fields.hasGlobalErrors() : 글로벌 errors 가 있으면 true, 없으면 false
- #fields.globalErrors() : 글로벌 에러(ObjectError)를 List 자료형으로 가져옴 - 위에서 구현한 검증 중 필드 오류(FieldError)가 있을 시 화면에 에러 메시지 출력
(price, quantity 같은 방식이므로 생략)
서버를 가동시켜 웹브라우저에서 HTML의 소스 코드를 보면 다음과 같다.<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>
▶ th:errorclass="field-error"<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>
- 해당 필드에 error가 있을 경우 class 속성에 field-error 추가
- ex) errors 있는 경우 → class="field-error field-error"
▶ th:errors="*{itemName}"
- 해당 필드에 error가 있을 경우 error message 출력
- th:if="${errors?.containsKey('itemName')}" th:text="${errors['itemName']}" 와 같다
참고
Thymeleaf - validation-and-error-messages docs
위 예시 코드로 검증 로직을 구현하였지만 문제가 있다. 검증에 위반하였을 때 다시 입력 폼으로 돌아가면 입력했던 값들이 유지가 되지 않고 사라진 것을 볼 수 있다. 만약 웹 페이지 사용자가 자신이 값이 검증 오류가 발생하여 다시 입력할때 입력했던 값이 사라져 자신이 입력한 값이 무엇인지 확인할 수 없게 된다.
그렇다면 검증에 위반하였을 때 입력된 값을 유지하는 방법을 알아보자.
FieldError, ObjectError
FieldError 의 생성자
public FieldError(String objectName, String field, String defaultMessage) {...} public FieldError(String objectName, String field, @Nullable Object rejectedValue, boolean bindingFailure, @Nullable String[] codes, @NullableObject[] arguments, @Nullable String defaultMessage) {...}
- objectName : 오류가 발생항 객체 이름
- field : 오류 필드
- rejectedValue : 사용자가 입력한 값(거절된 값)
- bindingFailure : 타입 오류 같은 바이딩 실패인지, 검증 실패인지 구분 값
- codes : 메시지 코드
- arguments : 메시지에서 사용하는 인자
- defaultMessage : 기본 오류 메시지
ObjectError 의 생성자
public ObjectError(String objectName, String defaultMessage) {...} public ObjectError( String objectName, @Nullable String[] codes, @Nullable Object[] arguments, @Nullable String defaultMessage) {...}
- objectName : 오류가 발생항 객체 이름
- codes : 메시지 코드
- arguments : 메시지에서 사용하는 인자
- defaultMessage : 기본 오류 메시지
즉, 입력된 값을 유지하기 위해서는 FieldError의 생성자를 위 예시에서 사용한 생성자가 아닌 사용자가 입력한 값을 저장하는 필드(rejectedValue)가 있는 생성자를 사용해주면 된다.
변경된 전체 코드
//검증 로직 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)); } }
타임리프의 사용자 입력 값 유지
th:field 는 검증에 통과했을 때는 th:obejct 의 필드 값을 사용하지만, 검증 오류가 발생하면 FieldError에서 보관된 rejectedValue 값을 이용하여 값을 출력한다.
기본적인 기능을 알아보았다. 추가 궁금한 기능은 해당 링크를 참조하길 바란다
참조 강의
728x90반응형'Spring > Spring MVC' 카테고리의 다른 글
Spring MVC - Validator 분리와 구현 (0) 2022.02.06 Spring MVC - 검증(Validation)에 errors MessageSource 사용 (0) 2022.02.06 Spring MVC - 검증(Validation) 직접 구현하기 (0) 2022.02.05 Spring MVC - 타임리프로 MessageSource에 등록된 메시지 사용하기 (0) 2022.02.05 Spring MVC - 스프링에서 제공하는 MessageSource 사용 방법 (메시지와 국제화 기능) (0) 2022.02.05