Free Lines Arrow
본문 바로가기
Spring/spring mvc 2 스터디

[Spring] Validation: BindingResult

by skahn1215 2022. 2. 1.
728x90
반응형

Validation

  • 검증 서버에 검증로직을 추가해보자
  • 한단계씩 코드를 개선해 보면서 어떻게 스프링에서 검증을 처리할수 있는지 
    알아본다.
  • 검증이 필요한 이유
    - 검증을 하지 않으면 로직상 치명적인 에러를 놓치기 쉽다.
    - 검증을 통해 사용자의 잘못된 값을 처리한다.

 

Map 으로 구현하기

  • 검증이 잘못 된경우 에러를 맵에 담아 컨트롤러에서 처리를 한다.
  • 모델에 에러 내용을 담아 화면으로 넘겨준다.

코드

@PostMapping("/add")
public String addItem(@ModelAttribute Item item, RedirectAttributes
redirectAttributes, Model model) {
    //Map을 이용하여 직접 에러검증하는 자료구조를 만든다.
    Map<String, String> errors = new HashMap<>();
    
    //검증 로직
    if (!StringUtils.hasText(item.getItemName())) {
        errors.put("itemName", "상품 이름은 필수입니다.");
    }
    
    //검증에 실패하면 다시 입력 폼으로
    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}";
}

화면

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

참고사항

  • errors?.containsKey('itemName')
    - errors 가 null 일 경우 NullPointerException이 발생하는 대신 null을 반환한다.

문제점

  • 중복처리가 많아 진다.
  • 타입오류가 처리가 안된다
    - 타입이 Integer일때 문자타입이 들어오는경우 등
  • 사용자가 입력한 값이 사라진다.
    - 사용자가 입력한 값을 별도로 저장후 보여줘야 뭐가 잘못되었는지 알수 있다,

 

BindingResult

  • BindingResult 로 Map 을 대체하였다.
  • 스프링이 제공하는 검증오류를 보관하는 객체이다.
    - 검증오류 발생시 여기에 저장이 된다.
  • BindingResult bindingResult 파라미터의 위치는 @ModelAttribute Item item 다음에 와야 한다.
  • BindingResult는 Model에 자동으로 포함이 된다.

사용이유

  • BindingResult를 사용하게 되면 400 오류가 발생하지 않고 에러를 담아 컨트롤러를 정상호출한다.
    - 그렇게 되면 오류페이지에 넘어가기 전에 처리를 할수 있다.

 

필드에러 처리

  • FieldError 를 BindingResult 에 추가해주면된다.
    - public FieldError(String objectName, String field, String defaultMessage) {}
    - objectName: @ModelAttribute 이름
    - field: 오류가 발생한 필드 이름
    - defaultMessage: 오류 기본메세지
@PostMapping("/add")
public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult,
    RedirectAttributes redirectAttributes) {
        if (!StringUtils.hasText(item.getItemName())) {
            bindingResult.addError(new FieldError("item", "itemName", "상품 이름은
                필수입니다.."));
         }
 
         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}";
}

 

글로벌 오류 처리

  • 필드의 값을 넘어서는 오류가 있다면 글로벌로 처리해주자
  • public ObjectError(String objectName, String defaultMessage) {}
    - objectName: @ModelAttribute이름
    - defaultMesssage: 오류기본 메세지
bindingResult.addError(new ObjectError("item", "가격 * 수량의 합은 10,000원 이상이어야
합니다. 현재 값 = " + resultPrice));

 

화면 에서 에러 처리

  • #fields: BindingResult가 제공하는 오류에 접근 할수 있다.
  • th:errors: 해당 필드에 오류가 있는 경우 태그를 출력한다.
     - 오류가 없으면 해당 태그는 출력되지 않는다.
  • th:errorclass: 에러가 있다면 class 정보를 추가한다
<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>

 

 

BindingResult 와 Errors

  • BindingResult는 인터페이스이다, Errors 인터페이스를 상속받고있다.
    - 실제 넘어오는 구현체는 BeanPropertyBindingResult 이다.
  • 둘다 구현이하고 있기 때문에 BindingResult 대신 Errors 사용해도 된다.
  • 하지만 관례상 BindigResult를 많이 사용한다.

 

FieldError, ObjectError

  • 위 예제 들에서는 사용자가 입력한 값이 날라가는 문제가 있다.
    FiedlError 와 ObjectError를 사용하여 이를 해결해 보자
  • 타임리프에서는 th:field가 오류발생을 감지 하여 해당값을 출력해주는 역할도 한다.

FieldError

  • 입력데이터가 컨트롤러의 @ModelAttribute 에 바인딩되는 시점에 오류발생시
    모델 객체에 사용자 입력 값을 유지하는것은 어렵다.
  • 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: 메시지에서 사용하는 인자.
  • defaultMessage: 기본 오류 메시지

예제

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

 

 

 

참고:

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

 

728x90
반응형

'Spring > spring mvc 2 스터디' 카테고리의 다른 글

[Spring] Validation: Validator  (0) 2022.02.02
[Spring] Validation: 오류코드, 메시지 처리  (0) 2022.02.01
[Spring] thymeleaf 예제  (0) 2022.01.06
[Spring] thymeleaf 기본 문법  (0) 2021.12.19
[Spring] thymeleaf 개요  (0) 2021.12.16

댓글