spring-validation 사용법 rest방식

2023. 3. 9. 17:19스프링

우선 build.gradle에 아래 의존성을 추가한다.

implementation 'org.springframework.boot:spring-boot-starter-validation'

일단 유효성 검사할 자바 entity에 체크할 annotation을 설정해주면 된다.

 

@NotBlank
private String tenantNm;

validation에서 제공해주는 어노테이션은 아래와 같다. 사용자가 annotation을 만들어서 커스텀도 가능하다.

// null만 허용
@Null(message="")

// null 허용 X,  "", " "는 허용
@NotNull(message="")

// null, "" 허용 X,  " "는 허용
@NotEmpty(message="")

// null, "", " " 허용 X
@NotBlank  (message="")

// 데이터의 사이즈(최소 길이, 최대 길이) 설정
@Size(min=, max= ,message="")

// 정규식을 이용해서 검사
@Pattern(regexp = ,message="")

// value 이하의 값만 허용
@Max(value = ,message="")

// value 이상의 값만 허용
@Min(value = ,message="")

// 값을 양수만 허용
@Positive(message="")

// 값을 양수와 0만 허용
@PositiveOrZero(message="")

// 값을 음수만 허용
@Negative(message="")

// 값을 음수와 0만 허용
@NegativeOrZero(message="")

// 현재보다 미래의 날짜만 허용
@Future(message="")

// 현재보다 과거의 날짜만 허용
@Past(message="")

// True일 때만 허용(null 체크 X)
@AssertTrue(message="")

// False일 때만 허용(null 체크 X)
@AssertFalse(message="")

 메시지값을 공용 properties에서 관리하고 싶다면 프로젝트 defalut properties 파일에 정의해주면 된다.

javax.validation.constraints.NotBlank.message = 필수 입력 값 입니다.

 

 

entity에서 작업을 마친 후에 컨트롤러에서는 검사할 entity에 @Valid를 붙이고 BindingResult 객체를 파라미터로 받는다.

BindingResult에서 error가 발생했을때 for문 돌아가며 반환할 오류메시지 및 필드명을 json으로 응답해준다.

public ResponseEntity<?> save(@Valid User user, BindingResult result) {
   if(result.hasErrors()) {
      ValidationError error = new ValidationError();
      error.setMessage("Validation failed");
      for (FieldError fieldError : result.getFieldErrors()) {
         error.addError(fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
      }
      return ResponseEntity.badRequest().body(error);
   }
   return ResponseEntity.ok(service.save(user));
}

ValidationError는 helper클래스로 따로 생성하였다. 

public class ValidationError  {
    private String message;
    private List<FieldError> errors;

    public ValidationError() {
    }

    public ValidationError(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public List<FieldError> getErrors() {
        return errors;
    }

    public void setErrors(List<FieldError> errors) {
        this.errors = errors;
    }

    public void addError(String objectName, String field, String message) {
        if (errors == null) {
            errors = new ArrayList<>();
        }
        errors.add(new FieldError(objectName, field, message));
    }
}

 

하지만 이 방법은 컨트롤러마다 BindingResult를 파라마터로 받아와야 하고 안에 로직도 똑같이 계속 반복해야 되기 때문에 Exception handler를 통해서 전역에서 처리해주기로 했다. validation 오류가 나면 BindException이 나기에 handler에서 잡아주고 기존 컨트롤러에서 처리하던 로직을 여기서 작성해주었다. 

MethodArgumentNotValidException은 컨트롤러에서 RequestBody로 받은 데이터일때 난다고 하는데 테스트 결과 폼데이터와 json방식 둘 다 BindException이 났다. 그래도 혹시 몰라 두 개 다 잡아줬다.  

@ExceptionHandler(BindException.class)
public ResponseEntity<ValidationError> handleValidationException(BindException ex) {
    return getValidationErrorResponseEntity(ex.getBindingResult());
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationError> requestBodyhandleValidationException(MethodArgumentNotValidException ex) {
    return getValidationErrorResponseEntity(ex.getBindingResult());
}

private ResponseEntity<ValidationError> getValidationErrorResponseEntity(BindingResult bindingResult) {
    ValidationError error = new ValidationError();
    error.setMessage("Validation failed");
    for (FieldError fieldError : bindingResult.getFieldErrors()) {
        error.addError(fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
    }
    return ResponseEntity.badRequest().body(error);
}

이렇게 사용하게 되면 컨트롤러엔 해당 entity에만 @Valid를 붙이고 사용하면 된다.