Giter Club home page Giter Club logo

validation's People

Contributors

ulzcq avatar

Watchers

 avatar

validation's Issues

오류코드와 메시지 처리 1

정리

  • 오류메시지를 체계적으로 관리하기!
  • FieldError의 생성자 파라미터 codes, arguments를 사용하자
    • 오류 발생시 오류코드로 메시지를 찾기 위해 사용된다.
  • errors.properties 파일을 생성해 오류 메시지를 입력해놓고 사용해본다.

image
image
image

Bean Validation 스프링 적용 ⭐

결론 정리

  • 앞서 우리가 직접 만든 ItemValidator를 제거하면, 애노테이션 기반 Bean Validation이 정상 동작한다.

    • 스프링 부트는 LocalValidatorFactoryBean을 글로벌 Validator로 등록한다.
    • 이 Validator는 NotNull 같은 애노테이션을 보고 검증을 수행한다.
  • 따라서 @Valid, @Validated만 적용하면 앞서 배운 검증 로직들이 실행된다.

    • 바인딩에 성공한 필드만 Bean Validation이 적용된다.
    • 바인딩에 실패시 typeMismatch FieldError를 추가하여 오류 메시지를 적용한다.
  • 기본으로 제공되는 오류 메시지를 변경하고 싶으면, error.properties에 메시지를 등록한다.

    • 오류 코드애노테이션 이름으로 등록된다.
  • 특정 필드(FieldError)가 아닌 해당 오브젝트 관련 오류(ObejctError)는 직접 자바 코드로 작성하는 것을 권장한다.


등록과 수정의 요구사항은 다를 수 있다.

  • 등록에는 필요없지만 수정에는 id가 필수이고, 폼에 추가적으로 들어오는 데이터도 있을 수 있다.

  • 그런데 Item 클래스는 공용이므로, 애노테이션으로 등록과 수정의 검증 조건을 동시에 적용할 수는 없다.

  • 해결법 두 가지 중 BeanValidation groups 기능을 알아본다.

    • *실무에서는 다른 방법을 주로 사용한다

image
image
image
image
image


image
image
image


image


image


image
image


image
image
image

검증 총 정리 💗

오류 발생시 기본적으로 :

  • 오류처리해야 하는 필드의 태그에는 class="field-error"를 붙인다
  • controller에서 폼에 빈 객체를 넘긴다.(Member) 이유는 여러가지가 있겠지만, 검증 실패시 이것을 재사용 할 수 있기 때문
  • 검증에 실패하면 다시 입력 폼으로 보낸다.

1. 직접 처리

오류메시지가 하나라도 있으면 model에 Map(errors)를 담고 입력폼으로 보냄
StringUtils를 사용해서 if문으로 하나하나 조건을 달고 에러메시지를 담아줌.

  • 필드에러: errors에 오류가 발생한 필드명(key), 오류(value) 저장
    • th:classappend="${errors?.containsKey('필드명')}?'field-error' : _"
    • field-error라는 클래스 정보를 더해서 색상 빨갛게, 에러없으면 _(No-Operation)으로 아무것도 안함
  • 글로벌 에러: globalError(key), 오류(value) 저장
    • th:if 문을 사용해 errors?.containsKey('globalError') 인 경우에만 오류메시지 출력
    • errors가 null이라면 NullPointerException 발생
    • 이것을 보완해 errors?.은 실패로 처리하고 오류메시지 출력 X (SpringEL)

문제점

  • 중복 처리 많음
  • 타입 오류 처리가 안됨(컨트롤러 진입 전 예외 발생, 바인딩 불가로 고객이 입력한 값 별도 관리 필요)

2. 스프링 검증 오류처리 방법

타입 오류로 바인딩에 실패하면 스프링은 FieldError를 생성하면서 사용자가 입력한 값을 넣어두고 오류를 BindingResult에 담아서 컨트롤러를 호출한다. 타임리프의 th:field는 오류가 발생하면 FieldError에서 보관한 값을 사용해 값을 출력

  • Java: BindingResultFieldError, ObjectError 사용
  • BindingResult는 검증해야 할 객체인 target 바로 다음에 와야한다.
  • 이미 본인이 검증해야 할 객체인 target을 알고 있다. rejectValue(), reject()를 사용하여 FieldError, ObjectError를 직접 생성하지 않고 검증 오류 다룰 수 있음
  • 오류 코드의 제일 앞단어만 입력해도 된다 (내부에서 MessageCodesResolver를 사용하여 여러 오류 코드를 자동으로 생성)
  • MessageCodesResolver는 구체적인 에러 코드를 먼저 만들고 덜 구체적인 것을 가장 나중에 만든다.
    • required.item.itemName(더 자세히 에러메시지를 작성할 시 이걸 추가하자)
    • required(덜 중요하면 이걸 재활용하고)
  • resoureces/errors.properties 파일을 생성해 오류메시지를 체계적으로 관리
#==ObjectError==
#Level1
totalPriceMin.item=상품의 가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
#Level2 - 생략
totalPriceMin=전체 가격은 {0}원 이상이어야 합니다. 현재 값 = {1}

#==FieldError==
#Level1
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
#Level2 - 생략
#Level3
required.java.lang.String = 필수 문자입니다.
required.java.lang.Integer = 필수 숫자입니다.
min.java.lang.String = {0} 이상의 문자를 입력해주세요.
min.java.lang.Integer = {0} 이상의 숫자를 입력해주세요.
range.java.lang.String = {0} ~ {1} 까지의 문자를 입력해주세요.
range.java.lang.Integer = {0} ~ {1} 까지의 숫자를 입력해주세요.
max.java.lang.String = {0} 까지의 문자를 허용합니다.
max.java.lang.Integer = {0} 까지의 숫자를 허용합니다.
#Level4
required = 필수 값 입니다.
min= {0} 이상이어야 합니다.
range= {0} ~ {1} 범위를 허용합니다.
max= {0} 까지 허용합니다.

#==타입 mismatch== 💛
typeMismatch.java.lang.Integer=숫자를 입력해주세요.
typeMismatch=타입 오류입니다.

#Bean Validation 추가 💛
NotBlank.item.itemName=상품 이름을 적어주세요.

NotBlank={0} 공백X
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}
  • application.properties 설정
# 메시지 설정
spring.messages.basename=errors
  •  타임리프
    • #fields : 오류가 발생하면 FieldError에서 보관한 값을 사용해 값을 출력
    • th:errors : 오류가 있으면 우선 순위가 가장 높은 오류 메시지 코드를 출력
    • th:errorclass

3. 스프링은 검증을 체계적으로 제공하기 위해 Validator 인터페이스 제공 : supports(), validate()

  • 구현 클래스를 생성해서 검증 로직을 controller에서 분리하자 (스프링 빈으로 주입 받아서 직접 호출)
  • 컨트롤러의 init 메서드(@InitBinder)에서 WebDataBinder에 Validator(검증기)를 추가하면 해당 컨트롤러에서는 검증기를 자동으로 적용
  • @Validated가 컨트롤러 메서드 파라미터에 붙으면 WebDataBinder에 등록한 검증기를 찾아서 실행. 이때 검증기 구분을 위해 supports()가 사용된다.

4. Bean Validation(빈 검증기)는 이런 검증 로직을 모든 프로젝트에 적용할 수 있게 공통화/표준화 한 것 💛

  • build.gradle 의존성 추가 필요
  • 일반적으로 사용하는 구현체는 하이버네이트 Validator (ORM과는 관련없음)
  • 오류 코드는 애노테이션 이름으로 등록된다
  • 특정 필드(FieldError)가 아닌 해당 오브젝트 관련 오류(ObejctError)는 직접 자바 코드로 작성하는 것을 권장
@NotBlank
@NotNull
@Range(min =1000, max = 100000) : 범위 안의 값이어야 함
@Max(9999) : 최대 9999까지만 허용

javax.validation으로 시작하면 자바 표준, org.hibernate.validator는 하이버네이트 validator 구현체를 사용할 때만 제공되는 검증 기능(실무에서는 대부분 하이버네이트 validator 사용)

5. 스프링은 이미 빈 검증기를 통합해두어서 직접 사용하지 않고 그냥 가져다 쓰면 된다. 💛

  • 앞서 만든 Validator 구현 클래스(검증 로직이 포함되어 있음)를 제거하자.
  • 스프링 부트는 LocalValidatorFactoryBean(애노테이션을 보고 검증을 수행)을 글로벌 Validator로 등록
  • Bean Validation 애노테이션만 적용하면 검증 로직들이 실행된다.
  • 바인딩에 성공한 필드만 Bean Validation이 적용된다. 바인딩에 실패시 typeMismatch FieldError를 추가하여 오류 메시지를 적용한다.

문제 : 등록과 수정의 요구사항은 다를 수 있다. (등록에는 필요없지만 수정에는 id가 필수임) 그런데 엔티티 클래스(예: Item)는 공용이므로, 애노테이션으로 등록과 수정에서의 검증 조건을 동시에 적용할 수는 없다.

해결: groups 기능은 실무에서 거의 쓰지 않고, 폼 객체를 분리하여 사용하는 방식을 주로 이용

예) Item 객체(Entity)의 검증코드(Bean Validation)을 모두 제거하고, ItemSaveForm과 ItemUpdateForm으로 분리하여 각각 등록/수정의 Bean Validation을 설정한다.

그러면 컨트롤러에서는 Form 객체로 전송 받아서 검증을 받고 Item 객체로 변환해서 Repository로 넘긴다.

컨트롤러 코드

    @GetMapping("/add")
    public String addForm(Model model) {
        model.addAttribute("item", new Item());
        //빈 객체를 넘긴 이유는 여러가지가 있겠지만, 검증 실패시 이것을 재사용할 수 있기 때문이기도 하다.
        return "validation/v4/addForm";
    }

    @PostMapping("/add")
    public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, BindingResult bindingResult, RedirectAttributes redirectAttributes) {

        //특정 필드가 아닌 복합 룰 검증, 나중에 메서드로 뽑는 게 더 좋을 듯!
        if (form.getPrice() != null && form.getQuantity() != null){
            int resultPrice = form.getPrice() * form.getQuantity();
            if(resultPrice < 10000){
                bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }

        //검증에 실패하면 다시 입력 폼으로
        if(bindingResult.hasErrors()){
            log.info("errors= {}", bindingResult);
            return "validation/v4/addForm"; //BindingResult는 자동으로 뷰로 넘어가므로 모델로 안넘겨도 된다
        }

        //성공 로직

        //form -> item 변환. setter 쓰지말고 생성자로 쓰는 것이 더 좋다.
        Item item = new Item();
        item.setItemName(form.getItemName());
        item.setPrice(form.getPrice());
        item.setQuantity(form.getQuantity());

        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/validation/v4/items/{itemId}";
    }

타임리프 단계별 적용:

글로벌 에러
1

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

2

<div th:if="${#fields.hasGlobalErrors()}">
    <p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">전체 오류 메시지</p>
</div>

필드에러
1 직접 처리(classappend 사용해서 길이 줄임)

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

2 th:errorclass, th:errors

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

오류코드와 메시지 처리 3,4,5,6 - MessageCodesResolver ⭐

정리

  • rejectValue() 호출

    • 내부에서 자동으로 MessageCodesResolver를 사용
  • MessageCodesResolver를 사용해서 검증 오류 코드로 메시지 코드들을 생성

    • 구체적인 에러 코드를 먼저 만들고 덜 구체적인 것을 가장 나중에 만든다.
    • required.item.itemName(더 자세히 에러메시지를 작성할 시 이걸 추가하자)
    • required(덜 중요하면 이걸 재활용하고)
  • new FieldError()를 생성하면서 메시지 코드들을 보관

  • th:errors에서 메시지 코드에서 메시지를 순서대로 찾고, 메시지를 노출


app 코드 변경할 필요없이 error.properties 파일만 변경하면 적용되므로 효율적이다.


  • 주로 타입 정보가 맞지 않을 경우 스프링이 직접 검증 오류 코드를 추가한다.
    스프링이 자동으로 생성하는 오류코드에 맞춰 error.properties에 원하는 오류 메시지를 추가하면 된다.

    • typeMismatch.java.lang.타입
    • typeMismatch
  • 타입오류의 경우 바인딩이 아예 실패해서, 로직 객체에 null이 들어가므로 다른 오류까지 뜨게 된다.
    타입 오류 메시지만 뜨게 하려면 추가로 코드 작성이 필요하다.


image
image

image
image
image
image

image
image
image

image
image
image

Validator 분리 ⭐

결론 정리

  • 스프링은 검증을 체계적으로 제공하기 위해 Validator 인터페이스를 제공한다.

  • Validator를 상속받는 ItemValidator 클래스를 생성해서 검증 로직을 컨트롤러에서 분리하자.

    • ItemValidator를 스프링 빈으로 주입 받는다. → @Component
    • controller의 init 메서드에서 WebDataBinder에 Validator(검증기)를 추가하면 해당 컨트롤러에서는 검증기가 자동으로 적용된다.
    • Validated 애노테이션이 메서드 파라미터에 붙으면 WebDataBinder에 등록한 검증기를 찾아서 실행한다.
    • 이때 검증기 구분을 위해 supports()가 사용된다.

image
image
image

image
image
image
image
image

Bean Validation - HTTP 메시지 컨버터 ⭐(JSON에 적용)

결론 정리

API 호출 시 JSON으로 데이터를 받아올 때HttpMessageConverter@RequestBody를 사용한다.

API의 Validation의 경우 다음 3가지 경우로 나뉜다.

  • 성공 요청
  • 실패 요청 : 예외 발생
  • 검증 오류 요청
    • bindingReuslt.getAllErrors();ObjectErrorFieldError를 반환
    • 에러 데이터에서 필요한 데이터만 뽑아서 별도의 API 스펙을 정의하고, 그에 맞는 객체를 만들어서 반환해야 한다.

HttpMessageConverter는 전체 객체 단위로 적용되므로, 메시지 컨버터의 작동이 성공해서 객체를 만들어야 @Valid, @Validated가 적용된다.
객체로 변경하지 못하면 예외가 발생하며 컨트롤러도 호출되지 않고 Validator도 적용할 수 없다.

예외 발생 시 원하는 모양으로 예외를 처리하는 방법은 이후 강의에서 다룬다.


image
image
image
image
image
image
image

오류코드와 메시지 처리 2

정리

  • FieldError, ObjectError를 직접 생성하지 않고 자동화하는 방법을 알아본다

  • rejectValue, reject 메서드 사용

    • 간단하게 필드명에러코드의 첫 단어만 파라미터로 넘기면 된다.

image
image
image
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.