Spring Boot에서의 Bean Validation (1)
Bean Validation은 Java 생태계에서 유효성(Validation) 검증 로직을 구현하기위한 사실상의 표준이다. Bean Validation은 Spring과 Spring Boot에도 잘 통합되어 있다.
해당 포스팅에서 사용된 예제 코드는 spring-boot validation example에서 확인 가능합니다.
Validation 설정
Spring Boot에서의 Bean Validation은 spring-boot-starter-validation
를 추가함으로써 사용할 수 있다.
1 | implementation('org.springframework.boot:spring-boot-starter-validation') |
Spring Dependency Management Gradle 플러그인(“io.spring.dependency-management”)을 사용한다면 현재 사용중인 Spring Boot 버전에서 자동으로 의존성(버전)을 가져오기 때문에 별도로 버전을 명시할 필요가 없다.
만약 spring-boot-starter-web
를 포함하고 있다면 Validation Starter도 포함되어 있기 때문에 따로 추가할 필요는 없다.
1 | implementation('org.springframework.boot:spring-boot-starter-web') |
Validation Starter는 Bean Validation Specification 구현체 중 가장 널리 사용되고 있는 hibernate validator를 포함하고 있다.
Bean Validation 기본
기본적으로 Bean Validation은 클래스 필드에 특정 어노테이션(annotation)을 달아 제약 조건을 정의하는 방식으로 동작한다. 그 후 해당 클래스 객체를 Validator를 이용해 제약 조건의 충족 여부를 확인한다.
Spring MVC Controller의 Validation
Spring RestController를 통해 Client로부터 전달받은 요청(request, input)에 대해 유효성을 검사하고자 한다. Client로부터 들어오는 다음의 3가지 요청 형식에 대해서 유효성 검사를 할 수 있다.
- request body
- path에 포함된 variables (ex. /foos/{id}의 id)
- query parameters
Request Body
POST 혹은 PUT 요청에서 request body에 JSON 형식의 데이터를 전달하는 것은 일반적이며, Spring은 JSON 형식의 데이터를 Java 객체에 자동으로 매핑한다. 이 과정에서 매핑되는 Java 객체가 요구 사항을 충족하는지 확인하려 한다.
1 |
|
numberBetweenOneAndTen는 1에서 10 사이의 값을 가져야 하며, notEmptyString은 빈 문자열(“”)이 아니어야 하고, pinCode는 6자리의 숫자를 가져야 한다.
request body에서 InputRequest
객체를 가져와 유효성 검사를 하는 RestController는 다음과 같다.
1 |
|
@RequestBody 어노테이션을 사용하고 있는 Input 매개변수에 @Valid
어노테이션만 추가하면 된다. 이로인해 Spring은 다른 작업을 수행하기 전에 먼저 객체를 Validator에 전달해서 유효성을 검사하게 된다.
만약
InputRequest
클래스에 유효성을 검사해야하는 다른 객체가 필드로 포함된 경우, 이 필드에도 @Valid 어노테이션을 추가해야한다. 이러한 경우를 Complex Type이라고 부른다.
유효성 검사에 실패할 경우 MethodArgumentNotValidException
예외가 발생한다. 기본적으로 Spring은 이 예외에 대해서 HTTP status code 400(Bad Request)으로 변환한다.
아래의 테스트 코드를 통해 동작을 확인할 수 있다.
1 |
|
Path Variables & Request Parameters
path에 포함된 variables과 query parameters에 대한 유효성 검사는 조금 다르게 동작한다.
Path Variable과 Request Parameter의 경우는 int와 같은 primitive type이거나 Integer 혹은 String과 같은 객체이기 때문에 복잡한 유효성 검사를 하지 않는다.
클래스 필드에 직접 어노테이션을 추가하지 않고 다음과 같이 Controller 메소드 매개 변수에 직접 제약 조건을 추가한다.
1 |
|
@Validated
어노테이션을 클래스 레벨의 Controller에 추가해 Spring이 메서드 매개 변수에 대한 제한 조건 annotation을 평가하게 해야한다.
request body 유효성 검사와 달리 실패할 경우 MethodArgumentNotValidException
예외가 아닌 ConstraintViolationException
예외가 발생한다. 주의할 것으로는 Spring은 ConstraintViolationException
예외에 대해서는 기본적으로 exception을 handling 하지 않기 때문에 HTTP status code 500(Internal Server Error)로 처리한다.
만약 HTTP status code 400(Bad Request)으로 처리하고자 한다면, custom exception handler를 사용하면 된다.
1 |
|
테스트 코드를 통해 동작을 확인해보자.
1 |
|
Spring Service의 Validation
Controller 레벨에서 입력을 검증하는것 뿐만 아니라 @Validated
와 @Valid
어노테이션을 이용해 다른 Spring component에서도 입력에 대해 유효성을 검증할 수 있다.
1 |
|
@Validated
어노테이션은 클래스 수준에서만 평가되기 때문에 메서드에는 추가하지 말아야 한다.
테스트 코드는 다음과 같다.
1 |
|
Spring Repository의 Validation
유효성 검사를 위한 가장 마지막 계층은 persistence layer이다. 기본적으로 Spring Data는 Hibernate를 사용하여 Bean Validation을 지원한다.
JPA Entities
InputEntity
클래스의 객체를 DB에 저장하려고 한다. 먼저 필요한 JPA 어노테이션들을 추가한다.
1 |
|
위의 Entity를 관리하는 CRUE Repository도 생성한다.
1 | public interface ValidateRepository extends CrudRepository<InputEntity, Long> {} |
기본적으로 제약 조건을 위반한 InputEntity
객체를 저장할 때 ConstraintViolationException
이 발생한다.
1 |
|
Bean Validation은 EntityManager
가 flush된 후에 트리거 된다. Hibernate는 특정 상황에서 EntityManager
를 자동으로 flush하지만 integration test의 경우에는 직접 수행해야한다.
만약 Spring Data Repository에서 Bean Validation을 비활성화하려면 Spring Boot property인 spring.jpa.properties.javax.persistence.validation.mode
값을 none
으로 설정하면 된다.
Spring Boot에서의 Bean Validation (1)
https://jongmin92.github.io/2019/11/18/Spring/bean-validation-1/