6.2 컬렉션 유효성 검증과 중첩 객체 @Valid
일반적인 평면(Flat) DTO 클래스의 필드 검증은 @NotBlank나 @NotNull 만으로 충분합니다. 하지만 회원 가입 시 [주소 리스트]를 받거나 주문 시 [상품 리스트]를 받는 등 **컬렉션(Collection)**이나 중첩된 내부 객체(Nested Object) 구조를 처리할 때는 조금 다른 접근이 필요합니다.
1. 중첩된 내부 객체(Nested Object) 검증
요청 JSON이 다음과 같이 계층적인 구조를 가질 때:
{
"name": "홍길동",
"age": 25,
"address": {
"city": "서울",
"zipcode": "12345"
}
}
내부 객체인 AddressDto의 필드 역시 검증을 수행하려면 부모 측 필드에 반드시 @Valid 애너테이션을 명시해야 연결된 하위 객체 검증기로 위임(Cascade)됩니다.
public class UserCreateRequest {
@NotBlank(message = "이름은 필수입니다.")
private String name;
@Min(value = 14, message = "14세 이상이어야 합니다.")
private int age;
@NotNull(message = "주소 정보는 누락할 수 없습니다.")
@Valid // ★이 애너테이션이 없으면 하위 객체의 내부 검증이 무시됨
private AddressDto address;
}
public class AddressDto {
@NotBlank
private String city;
@Size(min = 5, max = 5)
private String zipcode;
}
2. 컬렉션 (List, Set) 내부 원소 검증
회원이 등록할 취미 리스트나 스킬 리스트 등 원소(Element)의 개수가 여러 개인 컬렉션을 맵핑할 때, 리스트 자체의 검증과 리스트 안의 객체 하나하나의 검증을 동시에 해야 합니다.
public class UserCreateRequest {
// 배열/리스트 자체의 최소, 최대 길이를 제한
@Size(min = 1, max = 5, message = "취미는 1개 이상 5개 이하여야 합니다.")
@Valid // 컬렉션 안의 모든 하위 객체들에 대해 검증 수행
private List<HobbyDto> hobbies;
}
public class HobbyDto {
@NotBlank(message = "취미 명은 비어있을 수 없습니다.")
private String hobbyName;
}
이 때 핵심은 List 타입이나 커스텀 객체 래퍼 변수 위에 @Valid를 반드시 달아주어야, 루프를 돌며 내부 원소들의 룰을 검사한다는 것입니다.