[Spring] @Valid와 @Validated를 이용한 순차 검증

 

개요

 Spring에서 유효성 검증을 위해서 사용되는 어노테이션에는 흔히 @Valid@Validated가 있다. 필자는 평소에는 @Valid 위주로 사용했는데, 얼마 전 진행했던 프로젝트에서 @Validated를 사용한 좋은 검증 예시를 보아서 정리해보고자 한다.

 

 

@Valid

https://jakarta.ee/specifications/bean-validation/3.0/apidocs/jakarta/validation/valid

 

먼저 @Valid는 jakarta.validation(이전에는 javax.validation), 즉 Java 라이브러리 안에 속해있는 어노테이션이다. 

 

@Valid 어노테이션은 Bean Validation API와의 통합을 통해서 데이터 무결성을 유지하는 데에 필요한 유효성 검사를 수행할 수 있다.

 

Bean Validation API란?

@NotBlank, @Min, @Email과 같이 도메인 모델의 필드에 유효성 제약을 위한 어노테이션을 제공하는 API를 말한다. 표준으로는 JSR 303, JSR 380 등이 존재하며, 이를 구현한 대표적인 구현체가 Hibernate Validator이다.

(JPA의 구현체인 Hibernate 프레임워크와는 독립적인 프로젝트이다 - Hibernate ORM은 Hibernate Validator를 포함하기는 하지만, Hibernate ORM을 사용하지 않아도 Hibernate Validator를 사용할 수 있다)

 

 

 

@Validated

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/annotation/Validated.html

 

반면 @Validated 어노테이션은 org.springframework.validation 안에 있는 어노테이션이다.

 

설명을 보면 'Variant of JSR-303's Valid, supporting the specification of validation groups.'라고 되어있는 것을 확인할 수 있는데, 즉, @Validated 어노테이션은 @Valid의 확장이며, "Validation Group"을 지원한다는 사실을 알 수 있다.

 

 

Validation Group

 그렇다면  "Validation Group"이란 무엇일까? 흔히 Validation API를 사용하여 검증을 진행할 때, 위와 같이 Dto나 엔티티, 도메인 엔티티 등에 @NotNull, @NotBlank 등의 어노테이션을 사용하고, 컨트롤러에 @Valid 어노테이션을 사용해서 값을 검증한다. 

 

 

 

 그런데 만약 Dto의 각 필드에 대해서 검증을 순서대로 진행해서 사용자에게 반환해주고 싶다면, 각 Validation 실패 시에 터지는 예외를 다르게 잡아주어야 한다. 문제는 유효성 검증은 한번에 이루어지기 때문에, 검증 순서를 지정하기는 어렵다.

 

이를 Group ValidationGroup Sequence를 사용하여 해결할 수 있다.

Group Validation은 검증 규칙을 논리적인 그룹에 따라서 특정 상황에서만 적용할 수 있도록 하며, Group Sequence는 여러 검증 그룹을 순차적으로 검증하도록 하는 어노테이션이다.

 

 

1. 마커 인터페이스 생성

public class ValidationGroups {
    public interface LotsRewardValidation {};
}

 

 가장 먼저 검증 그룹을 분류할 마커 인터페이스를 생성해준다. 해당 인터페이스는 말 그대로 '마킹'하는 역할만을 하며, Bean Validation API에 명시해서 사용할 수 있다.

 

2. @GroupSequence 어노테이션을 통해서 검증 그룹의 검증 순서 정의

@GroupSequence({Default.class, ValidationGroups.LotsRewardValidation.class})
public interface LotsValidationSequence {
}

 

@GroupSequence 어노테이션은 검증 그룹의 검증 순서를 정의한다. 즉, 지정된 @GroupSequence 어노테이션은 검증 그룹의 검증 순서를 정의하며, 이를 통해 의존성 있는 검증 규칙을 체계적으로 적용할 수 있다.

 

Default.class란 기본 검증 그룹을 말하며, 여기서 기본 검증이 완료되었다면 추가로 ValidationGroups.LotsRewardValidation.class라는 검증 그룹이 동작하게 된다. 이렇게 하면 Default 그룹의 검증이 성공한 후에 LotsRewardValidation 그룹의 검증을 수행하게 된다.

 

만약 Default 그룹의 검증 중에 하나라도 실패하면, 이후 그룹의 검증은 수행되지 않는다.

 

3. DTO에 그룹 지정

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ConfigEventRewardRequestDto {

    @NotEmpty(message = "상품 리스트가 비어있습니다.")
    @Valid
    List<@Valid EventRewardDto> eventRewardList;

    @Valid
    @NotNull(message = "가중치가 비어있습니다.", groups = ValidationGroups.LotsRewardValidation.class)
    EventWeightDto eventWeight;
}

 

 Dto의 검증 어노테이션의 'groups' 옵션에 마커 인터페이스를 설정해준다. 여기서 eventRewardList는 Default 검증 그룹에, eventWeight는 LotsRewardValidation 검증 그룹에 속한다.

 

 

4. 컨트롤러에서 @Validated 사용

@PutMapping("/lots/rewardconfig")
@Operation(summary = "랜덤추첨 이벤트 상품 수정 Api")
@ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "성공적인 응답"),
        @ApiResponse(responseCode = "400", description = "요청 형식 에러 - 형식이 잘못되었습니다."),
        @ApiResponse(responseCode = "500", description = "이벤트를 찾을 수 없습니다."),
})
public CommonResponse<ConfigEventRewardResponseDto> configLotsEventReward(@RequestBody @Validated(LotsValidationSequence.class) ConfigEventRewardRequestDto dto){
    return new CommonResponse<>(adminService.configLotsEventReward(dto));
}

 

@Validated에 그룹 시퀀스를 설정해주어 검증 그룹이 순차적으로 검증되도록 할 수 있다. 즉, ConfigEventRewardRequestDto에 대해서

1. eventRewardList의 @NotEmpty여부를 검증한 후, 리스트를 구성하는 EventRewardDto의 @Min, @NotBlank를 검사
2. eventWeight의 @NotNull여부를 검사하고, EventWeightDto 내부의 @Min과 @NotBlank 여부를 검사

 

의 순서로 검증이 이루어지게 된다.

 

 

5. 검증 결과 확인

 

 

eventRewardList과 eventWeight를 모두 null로 해서 보내도, Default 검증 과정에서 오류가 발생했으므로 eventRewardList에 대한 검증만 이루어지고, LotsRewardValidation 그룹에 대한 검증은 이루어지지 않는 것을 확인할 수 있다. 

 

 

 

참고 - https://dncjf64.tistory.com/302

추가 예정 - https://medium.com/sjk5766/valid-vs-validated-%EC%A0%95%EB%A6%AC-5665043cd64b

 

@Valid vs @Validated 정리

아래 결론부터 정리하고 내용을 보도록 하겠다.

medium.com