유효성 검사

  • 웹 브라우저 : 자바스크립트로 웹서버에 전송하기 전에 검사한다.
  • 웹 서버 : 전달받은 요청 파라미터를 검사한다.

이번 글에서는 request 후에 서버 측에서 데이터를 바인딩할 때 데이터가 유효한지 검사하는 경우를 설명하겠습니다.

 

BindingResult가 스프링이 제공하는 검증 오류 처리의 핵심입니다.

 

이유는 BindingResult는 스프링이 제공하는 검증 오류를 보관하는 객체이기 때문입니다.

 

데이터 유효성 검사를 실패하면 ConstraintViolationException을 발생시킵니다.

 

데이터가 유효하지 않은 속성이 있으면 그에 대한 에러 정보를 BindingResult에 담습니다.

 

정상적인 동작에서는 BindingResult에 담은 오류 정보를 가지고 컨트롤러를 호출합니다.

 

하지만 BindingResult가 없다면 4xx 오류가 발생하면서 컨트롤러가 호출되지 않고 오류 페이지로 이동하게 됩니다.

 

이런 BindingResult에 검증 오류를 적용하는 방법은 3가지가 있습니다.

  1. @ModelAttribute의 객체에 타입 오류 등으로 바인딩이 실패하는 경우 스프링이 FieldError을 생성해서 BindingResult에 넣어준다.
  2. new FieldError를 만들어서 개발자가 직접 넣어준다.
  3. Validator를 사용(@Valid, @Validated)

보통은 @ModelAttribute를 검증하기 때문에 BindingResult를 @ModelAttribute 뒤에 두고 사용합니다.

 

첫 번째 방식으로 BindingResult 메소드를 사용하는 방식이 있습니다.

  • boolean hasErrors() : 에러의 유무를 판단한다.
  • boolean hasGlobalErrors() : 글로벌 에러의 유무를 판단한다.
  • void addError(ObjectError error) : field, type, value 등의 에러를 출력할 수 있다.
  • void rejectValue() : field, errorCode, defaultMessage 등을 받아서 reject 됐을 때 데이터를 남길 수 있다.
  • 이외에도 많은 것들이 있습니다.

주로 전달하는 파라미터

  • objectName : 오류가 발생한 객체의 이름
  • field : 오류 필드
  • rejectedValue : 사용자가 입력한 값(거절된 값)
  • bindingFailure : 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값
  • codes : 메시지 코드
  • arguments : 메서지에서 사용하는 인자
  • defaultMessage : 기본 오류 메시지

두 번째 방법으로 Validator를 사용하는 방식도 존재합니다.

 

@Valid 혹은 @Validated 애노테이션을 이용해서 데이터 유효성 검증을 할 수 있습니다.

 

@Valid는 자바 표준 검증 애노테이션이고, @Validated는 스프링 전용 검증 애노테이션입니다.

 

@Valid, @Validated는 거의 동일한 역할을 합니다.

 

정확히 말하면, @Validated는 group 기능이 추가된 @Valid입니다.

 

하지만 @Validated의 group 기능은 잘 쓰지는 않습니다.

 

이유는 데이터를 한곳에서 가지고 오지 않고, 다른 데이터를 함께 사용하기 때문입니다.

 

그리고 Bean Validation이라는 데이터 유효성 검사 프레임워크를 사용할 수도 있습니다.

 

다양한 제약 조건을 도메인 모델에 애노테이션을 사용해서 정의할 수 있게 합니다.

 

이 제약 조건을 유효성 검사가 필요한 객체에 직접 정의하는 방법으로 기존 유효성 검사 로직의 문제를 해결합니다.

 

주의할 점은 데이터 유효성 감사를 진행할 때 검사가 중복으로 실행되지 않도록 해야 합니다.

 

같은 데이터 유효성 검사가 여러 번 실행될 경우 애플리케이션 성능이 저하될 수 있기 때문입니다.

 

제약조건 애노테이션

  • @Length : 길이를 설정할 수 있다.
  • @NotBlank : 빈 문자열을 막을 수 있다.
  • @NotNull : 널을 방지할 수 있다.
  • @NotEmpty : 해당 값이 Null이 아니고, 빈 스트링("")이 아닌지 검증함.
  • @Range : 범위를 설정할 수 있다.
  • @Max / @Min : 최대/최소를 설정할 수 있다.
  • @Email : 이메일 형태의 데이터를 확인합니다.(@ 포함 여부 확인)

마지막으로, 타임리프에서도 검증 오류를 쉽게 표현하는 기능을 제공합니다.

  • $fields는 BindingResult가 제공하는 검증 오류에 접근할 수 있다.
    • th:field="*{itemName}" -> id, name, value를 알아서 만들어준다.
  • th:errors : 해당 필드에 오류가 있는 경우에 태그를 출력한다. th:if의 편의 버전
    • th:errors="*{itemName}" -> 해당 필드에 오류가 있는 경우에 태그를 출력한다.
  • th:errorclass : th:field에서 지정한 필드에 오류가 있으면 class 정보를 추가한다.
    • th:errorclass="field-error" -> 오류가 나면 해당 클래스를 출력하겠다.

 

public class Item {

  private Long id;

  @NotBlank
  private String itemName;

  @NotNull @Range(min = 1000, max = 1000000)
  private Integer price;

  @NotNull @Max(value = 9999)
  private Integer quantity;

}

----------------------------------------------------------------------------
@PostMapping("/add")
public String add(@Validat @ModelAttribute Item item, BindingResult bindingResult) {
  
  if (bindingResult.hasErrors()) {
    return "error"; //보통 되돌리기
  }
  
  return "success";//성공 페이지로 이동 혹은 redirect로 원하는 페이지로 이동
  
}

----------------------------------------------------------------------------
<form action="item.html" th:action th:object="${item}" method="post">
  <div>
    <label for="itemName" >상품명</label>
    <input type="text" id="itemName" th:field="*{itemName}" 
           th:errorclass="field-error" placeholder="이름을 입력하세요">
           
    <div class="field-error" th:errors="*{itemName}">상품명 오류</div>
  </div>
</form>