Back-End/Spring

[Spring MVC2][검증(Validation)] - FieldError, ObjectError(부제: 사용자 입력 오류에도 입력해 놓은 데이터를 그대로 유지해보자)

얄루몬 2022. 4. 27. 09:42

💻본 포스팅은 '스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 김영한'님의 강의를 듣고 작성되었습니다.

https://inf.run/vQHp

 

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

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

www.inflearn.com


[FieldError, ObjectError]

[입력 오류에도 데이터가 사라지지 않고 그대로 남겨지게 해보자]

  • BindingResult를 사용해서 데이터를 바인딩 할 때 오류에도 제대로 컨트롤러를 호출하게 하는 원리와 방법을 알아보았다. 
  • 그러나 이런 편의점에도 불구하고 우리는 사용자 입력 오류에 입력한 값 그대로 남아있는 것이 아닌 사라지는 현상을 발견할 수 있었다. 
  • 이를 해결하고자 입력한 값이 남아있도록 하는 방법을 알아보려 한다.

[컨트롤러]

//V2 = 입력값이 올바르지 않아도 데이터를 그대로 화면에 남길 수 있는 코드드
    @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() > 10000) {
            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) {
                //실제 데이터가 넘어가지 않아서 얘는 bindingFailure이 필요 없음.
                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,ObjectError)]

[FieldError]

  • 오류 발생시 사용자 입력 값을 저장하는 기능을 제공한다.
    • 가격에 숫자가 아닌 문자가 입력될 경우 가격은 Integer 타입으로 문자를 따로 보관할 방법이 없다.
    • 그래서 오류가 발생할 경우 사용자 입력 값을 보관하는 별도의 방법이 필요하다.
    • 이때 별도로 입력값을 보관하게 되면, 보관한 사용자의 입력 값을 검증 오류가 발생했을 때 다시 화면에 출력해주면 된다.
    • 이 사용자 입력 오류값을 별도로 보관 역할을 하는 것이 FieldError다.

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

  • 이 역시도 FieldError와 유사하게 두 가지의 생성자를 제공한다.

 

[스프링의 바인딩 오류 처리]

  • 타입 오류로 바인딩에 실패 시
    1. 스프링은 FieldError를 생성
    2. 생성한 FieldError 내에 사용자가 입력한 값을 넣어둔다.(타입 오류로 바인딩 실패한 값)
    3. 해당 오류를 BindingResult에 담아서 컨트롤러를 호출한다.
    4. 위의 과정으로 타입 오류 같은 바인딩 실패시에도 사용자의 오류 메시지를 정상 출력

 

[타임리프의 사용자 입력 값 유지]

  • th:field="*{price}"
  • 타임리프의 th:field
    • 정상 상황에는 모델 객체의 값을 사용
    • 오류가 발생하면 FieldError에서 보관한 값을 사용해서 값을 출력한다