들어가는 말

2부는 @validated 에 예제가 있으면 @Valid 예제가 보고싶다면 아래 링크로 이동해주세요
하지만 큰제목 1~ 2에 대한 내용은 1부와 동일합니다.

2022.09.01 - [JAVA/스프링(Spring)] - [Spring] @Valid, @Validated 에 대해서 정리[1] - @Valid 예제


1. Gradle 및 maven 

메이븐저장소로 이동해 원하는 버전의 Hibernate Validator Engine 라이브러리를 다운 받으시면 됩니다.

// validator
implementation group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.2.0.Final'JAVA

 

 

 

2. @Valid와 @Validated 대해서

 

@Valid

JSR-303 표준 스펙으로써 빈 검증기(Bean Validator)를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션이며,  RequestResponseBodyMethodProcessor를 통해 validation을 진행하고 검증에 오류가 있으면MethodArgumentNotValidException이 발생합니다. 

스프링 ExceptionResolver의 DefaultHandlerExceptionResolver덕분에 400 에러를 발생시킵니다.

 

@Validated 

@Validated는 AOP 기반으로 메소드 요청을 인터셉터하여 처리된다. @Validated를 클래스 레벨에 선언하면 해당 클래스에 유효성 검증을 위해 인터셉터(MethodValidationInterceptor)가 받아서 처리합니다. 그리고 해당 클래스의 메소드들이 호출될 때 AOP의 포인트 컷으로써 요청을 가로채기 때문에 스프링 빈이라면 유효성 검증을 진행할 수 있습니다.

 

 

 

 

3. 제약 조건 어노테이션

Anotation 제약 조건
@NotNull 모든 데이터 타입에 대해 null을 허용하지 않는다.
@NotEmpty null과 ""를 허용하지 않는다. (타입 - String, Collection. Map, Array)
@NotBlank null과 "", " "(빈 공백 문자열)을 허용하지 않는다.
@Null Null만 입력 가능
@Size(min=,max=) 문자열, 배열등의 크기 검증
@Pattern(regex=) 정규식 검증
@Max(숫자) 최대값 검증
@Min(숫자) 최소값 검증
@Future 현재 보다 미래인지 검증
@Past 현재 보다 과거인지 검증
@Positive 양수만 가능
@PositiveOrZero 양수와 0만 가능
@Negative 음수만 가능
@NegativeOrZero 음수와 0만 가능
@Email 이메일 형식만 가능
@Digits(integer=, fraction = ) 대상 수가 지정된 정수와 소수 자리 수 보다 작은지 검증
@DecimalMax(value=) 지정된 실수 이하인지 검증
@DecimalMin(value=) 지정된 실수 이상인지 검증
@AssertFalse false 인지 검증
@AssertTrue true 인지 검증

 

 

 

@Validated  사용해보기

다른 사용자들의 예제를 보면 @Validated를 클래스에 사용하지만 저는 매개변수에 선언을 하는 예제로 사용합니다. 

 

UserValidGroup 

인터페이스안에 그룹화 할 내부 인터페이스 정의해주기

public interface UserValidGroup {
    public interface UserCreate{}  //유저를 생성할 때 사용할 그룹
    public interface UserUpdate{}  //유저의 정보를 수정할 때 사용할 그룹
}

 

 

ValidTestController 

@Validated에 인터페이스로 정의해준 UserValidGroup.UserUpdate.class를 선언해준 것을 볼 수 있습니다.  이유는 바로 다음에 볼 수 있습니다.

@RestController
public class ValidTestController {

    /*
    *  @Validated를 이용해 그룹화하여 검증
    * 이메일: 이메일 형식으로 입력을 받아야 한다.
    * 이름: 사용자의 이름은 null이면 안된다.
    * */
    @PostMapping("/userUpdateTest")
    public void UserNameUpdate(
            @RequestBody
            @Validated(UserValidGroup.UserUpdate.class) UserRequestDto userRequestDto) {
        System.out.println("로직 수행 중");
        System.out.println("변경 완료");
    }
}

 

 

UserRequestDto

1부에서는 제약 어노테이션만 있었지만 아래코드는 group과 messge를 선언해준 것을 확인할 수 있습니다. 
ValidTestController에서 @Validated(UserValidGroup.UserUpdate.class)를 해준이유는 검증할 객체의 제약코드중 그룹이 
UserValidGroup.UserUpdate.class로 되어있는 제약만 검증하도록 설정해준 겁니다.

@Getter
@NoArgsConstructor
public class UserRequestDto {

    @Email(groups = UserValidGroup.UserUpdate.class, message = "이메일 형식이 아닙니다.")
    private String email;

    @NotBlank
    private String password;

    @NotNull(groups = UserValidGroup.UserUpdate.class, message = "name은 null이여서는 안됩니다.")
    private String name;

    @Min(19)
    private int age;

 

 

테스트 코드

첫번째와 두번째 코드는 검증에 실패하므로 400 BadRequest가 발생한다. 
세번째는 성공코드로 name과 email만 있어도 성공하는지 확인하기 위한 코드이다.

@DisplayName("@Validated 테스트")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Nested
class Validated {

    @DisplayName("이메일 형식 실패")
    @Order(1)
    @Test
    public void 이메일_형식_실패() throws Exception {
        UserRequestDto userRequestDto = UserRequestDto.builder()
                .email("whitewise")
                .age(19)
                .name("짱구")
                .password("1234")
                .build();

        String requestBody = mapper.writeValueAsString(userRequestDto);
        HttpEntity<String> stringHttpEntity = new HttpEntity<>(requestBody, headers);

        //when
        ResponseEntity response = restTemplate.postForEntity(
                "/userUpdateTest",
                stringHttpEntity,
                Void.class
        );

        //then 400 BAD_REQUEST가 확인되어야 정상
        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
    }

    @DisplayName("이름 null 실패")
    @Order(2)
    @Test
    public void 이름_null_실패() throws Exception {
        UserRequestDto userRequestDto = UserRequestDto.builder()
                .email("whitewise95@gmail.com")
                .age(19)
                .password("1234")
                .build();

        String requestBody = mapper.writeValueAsString(userRequestDto);
        HttpEntity<String> stringHttpEntity = new HttpEntity<>(requestBody, headers);

        //when
        ResponseEntity response = restTemplate.postForEntity(
                "/userUpdateTest",
                stringHttpEntity,
                Void.class
        );

        //then 400 BAD_REQUEST가 확인되어야 정상
        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
    }

    @DisplayName("Validated 성공")
    @Order(1)
    @Test
    public void Validated_성공() throws Exception {
        UserRequestDto userRequestDto = UserRequestDto.builder()
                .email("whitewise@gmail.com")
                .name("짱구")
                .build();

        String requestBody = mapper.writeValueAsString(userRequestDto);
        HttpEntity<String> stringHttpEntity = new HttpEntity<>(requestBody, headers);

        //when
        ResponseEntity response = restTemplate.postForEntity(
                "/userUpdateTest",
                stringHttpEntity,
                Void.class
        );

        //then 400 BAD_REQUEST가 확인되어야 정상
        assertEquals(HttpStatus.OK, response.getStatusCode());
    }

}

 

테스트 결과

콘솔을 1부와 같게 비슷하게 나온걸 확인 할 수 있습니다. 단지 message만 자신이 정의해준 내용으로 나옵니다.

 

 

 

번외

개발을 하다보면 같은 객체를 사용해야할 때가 많이 있다. 위에 예제는 update할 때 였지만 user를 생성할 때 사용할 경우는 UserCreate 인터페이스를 사용하면 된다. 계속 보자

public interface UserValidGroup {
    public interface UserCreate{}  //유저를 생성할 때 사용할 그룹
    public interface UserUpdate{}  //유저의 정보를 수정할 때 사용할 그룹
}

 

 

UserRequestDto

UserUpdate뿐만 아니라 group 에 { } 를 감싸서 , 로 구분하고 UserCreate도 사용할 수 있게 되었습니다.
이런 방법이라면 create, delete, update, 더 간다면 Read 뿐만 아니라 여러 API에 하나의 객체로도 많은 다양한 검증을 
시도할 수 있습니다.

@Getter
@NoArgsConstructor
public class UserRequestDto {

    @Email(groups = { UserValidGroup.UserUpdate.class, UserValidGroup.UserCreate.class }, message = "이메일 형식이 아닙니다.")
    private String email;

    @NotBlank(groups = UserValidGroup.UserCreate.class, message = "패스워드는 공백만 있으면 안됩니다.")
    private String password;

    @NotNull(groups = { UserValidGroup.UserUpdate.class, UserValidGroup.UserCreate.class }, message = "name은 null이여서는 안됩니다.")
    private String name;

    @Min(groups = UserValidGroup.UserCreate.class, value = 19, message = "나이는 19세 이상만 가능합니다.")
    private int age;

 

 

ValidTestController

UserCreate 메소드를 생성해 UserValidGroup.UserCreate.class 로 group 해준 제약 어노테이션만 검증하도록  @Validated 를 사용해준 것을 볼 수 있습니다.

@RestController
public class ValidTestController {

    @PostMapping("/userUpdateTest")
    public void UserNameUpdate(
            @RequestBody
            @Validated(UserValidGroup.UserUpdate.class) UserRequestDto userRequestDto) {
        System.out.println("로직 수행 중");
        System.out.println("변경 완료");
    }


    @PostMapping("/userCreatTest")
    public void UserCreate(
            @RequestBody
            @Validated(UserValidGroup.UserCreate.class) UserRequestDto userRequestDto) {
        System.out.println("로직 수행 중");
        System.out.println("유저 생성");
    }
}

 

 

전체 코드

 

GitHub - whitewise95/ExampleCollection: 블로그에 정리한 글 예제들

블로그에 정리한 글 예제들. Contribute to whitewise95/ExampleCollection development by creating an account on GitHub.

github.com

 

 

 

1부 -  @Valid예제

 

1부 [Spring] @Valid, @Validated  에 대해서 정리[1] - @Valid예제 - 보러가기

2부 [Spring] @Valid, @Validated  에 대해서 정리[2] - @Validated 예제 - 현재

 

 

참고한 자료

 

 

[Spring] @Valid와 @Validated를 이용한 유효성 검증의 동작 원리 및 사용법 예시 - (1/2)

Spring으로 개발을 하다 보면 DTO 또는 객체를 검증해야 하는 경우가 있습니다. 이를 별도의 검증 클래스로 만들어 사용할 수 있지만 간단한 검증의 경우에는 JSR 표준을 이용해 간결하게 처리할 수

mangkyu.tistory.com

 

 

@Valid와 @Validated

2021-04-30글 @Valid와 @Validated 서비스 근로에서 장바구니 미션 API를 만들며 요청으로 들어온 DTO의 값을 검증하는 방법을 고민하다가, Spring Validation을 사용해보게 되었다. 이번에는 DTO의 필드에 제약

newwisdom.tistory.com

 

 

Spring - @Validated 란? 무엇인가? / @Valid와 차이점

@Validated를 알아보자 안녕하세요. 고코더 입니다. 오늘은 스프링에서 데이터 유효성 검증에 사용하는 @Validated 라는 어노테이션을 배워 보려고 합니다. 오래전부터 자바를 쓰신 분들은 @Valid 라는

gocoder.tistory.com

 

Spring의 @Valid, @Validated

스프링에서 @Valid 어노테이션을 이용해서 객체를 검증할 수 있다. 객체에 정의된 제약사항(@NotBlank, @Max 등)을 이용하여 검증할 수 있으며 객체 뿐 아니라 메서드의 파라미터, 반환값도 검증할 수

velog.io

 

복사했습니다!