1. 들어가며
API관련 작업을 하다가, @RequestBody로 매핑된 객체의 필드값을 Validation하기 위해서 @Valid
어노테이션(JSR-380) 을 적용했지만, 제대로 적용이 되지 않았다. 문제상황과 그 해결책을 알아 보자.
2. JSR-303? JSR-380?
들어가기에 앞서 JSR-380은 Bean Validation2.0으로써, Java Bean들에 대한 유효성(Validation)을 검증하는 Spec을 정의한다. 대표적으로 @Notnull, @Min, @Max와 같이 Java Bean들의 필드값들을 정의하는데 사용한다.
관련 포스팅은 여기🔗에서 확인 가능하다!
- JSR-303은 Bean Validation 1.0 스펙
- JSR-349는 Bean Validation 1.1 스펙
- JSR-380은 Bean Validation 2.0 스펙
3. 예제 소스 코드
web과 lombok를 추가한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
특히나 스프링부트 2.3버전 이후로는 javax.validation.*
의 validation이 기본이 포함되어 있지 않기 때문에 다음과 같이 starter-validation 모듈을 추가한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@RestController
@RequestMapping("/api/account")
public class AccountRestApi {
@PostMapping
public ResponseEntity<?> createAccount(@RequestBody @Valid Account account) {
return ResponseEntity.ok(account);
}
}
간단한 Account REST API를 작성한다.
Account객체를 요청 바디로 지정하고, /api/account
를 호출하면 객체 그자체를 응답하는 간단한 REST API 이다.
여기서 @Valid
어노테이션으로 지정했기 때문에, 스프링에서 Account 객체의 필드값(property)에 대해서 validation을 체크한다.
@Getter
@Setter
public class Account {
@NotNull(message = "name must not be null")
private String name; // 필수값
@JsonUnwrapped //Composite 객체 풀어서
private PersonalInfo personalInfo;
}
- Account는 다음과 같고,
name
필드에는 반드시 필수값이어야 하는@Notnull
어노테이션을 추가한다. 예외가 발생할 때 예외 메세지도 지정했다. - PersonalInfo의
@JsonUnwrapped
를 한 이유는 요청을 보낼때, 중첩된 json형식이 아니라, 풀어서 Flat한 형식으로 보낼 수 있도록 하도록 어노테이션을 지정했다.
@Getter
@Setter
public class PersonalInfo {
@NotNull(message = "email must not be null")
private String email;
private String address;
}
- PersonalInfo는 email과 address 필드로 구성된다.
- 정리하면 name, email은 필수값, address는 선택값이다.
{
"name": "andrew",
"email": "umanking@gmail.com",
"address": "서울"
}
실제 API 요청을 POST메서드, Body는 json형식으로 위와 같이 보낼 수 있다. 이 상황에서 email값을 빼고 보내면, email은 필수값이기 때문에 @NotNull 어노테이션때문에 BadRequest가 나와야 한다. 하지만 성공한다. email 필드에 대한 validation이 제대로 되지 않았음을 의미한다.
4. 문제상황
객체 상속이나, 포함관계로 구성하는 경우에는 해당 필드에 @Valid 어노테이션을 달라고 문서에 나와있다.
5. 해결책
@Getter
@Setter
public class Account {
@NotNull(message = "name must not be null")
private String name;
@JsonUnwrapped
@Valid //얘를 추가해줘야 한다.
private PersonalInfo personalInfo;
}
다음과 같이 포함관계로 구성된 필드에도 @Valid 어노테이션을 추가해줘야한다.
해당 소스 코드는 여기🔗에서 확인 가능합니다.
6. 정리
- 한번 경험하면 간단한 내용인데, 요청바디로 전송하는 객체의 필드들이 다양하고, 리팩토링으로 객체를 쪼개면서 해당 이슈 상황이 발생했다.
- 관련 키워드 검색은 hibernate, bean validation, object graph 정도로 검색해야지 해당 문서에서 확인 가능하다.
7. 참고
- https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes
- https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-object-graph-validation