들어가는 말

@validated @Valid 사용하면 비지니스 로직에 
 유효성 체크하는 코드가 줄어들어 
전체적인 비지니스 로직이 간결해지는 것을 많이 느꼈다.
하지만 항상 쓸 때마다 까먹어 전에 했던 프로젝트를 계속 보는데 
이곳 저곳에서 보는것이 매우 불편해,
블로그에 정리하여 한방에 보기위해 글을 정리했습니다.

 


Gradle 및 maven 

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

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

 

 

 

@Valid와 @Validated 대해서

 

@Valid

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

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

 

@Validated 

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

 

 

 

 

제약 조건 어노테이션

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 인지 검증

 

 

 

 

@Valid 사용해보기

 

검증할 객체에 제약어노테이션을 사용한다 

@Email - 이메일 형식만 받을 수 있다.
@NotBlank - 빈문자열 불가능  ...ex) "", " "
@NotNull - null은 허용이 안된다.
@Min  - 최소 19이상의 숫자여야한다.

@Getter
@NoArgsConstructor
public class UserRequestDto {

    @Email
    private String email;

    @NotBlank
    private String password;

    @NotNull
    private String name;

    @Min(19)
    private int age;
}

 

 

컨트롤러에 @Valid 사용하기

@Vaild 어노테이션을 사용해 UserRequestDto에 제약을 검증한다.

@RestController
public class ValidTestController {

    @PostMapping("/validTest")
    public void validTest(@RequestBody @Valid UserRequestDto userRequestDto) {
        System.out.println("로직 수행");
        System.out.println(userRequestDto);
    }
}

 

 

테스트코드 적용

전부 제약조건에 불만족하는 테스트이며 400 BadRequest 에러가 발생한다.

∨ (접은글)테스트코드

더보기
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ValidTestControllerTest {

    @Autowired
    private TestRestTemplate restTemplate;

    private ObjectMapper mapper;
    private HttpHeaders headers;

    @BeforeAll
    public void setUp() {
        headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        mapper = new ObjectMapper();
    }

    @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(
                "/validTest",
                stringHttpEntity,
                Void.class
        );

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

    @DisplayName("나이 19세 미만으로 실패")
    @Order(2)
    @Test
    public void 나이_19세_미만으로_실패() throws Exception {
        UserRequestDto userRequestDto = UserRequestDto.builder()
                .email("whitewise95@gmail.com")
                .age(18)
                .name("짱구")
                .password("1234")
                .build();

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

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

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

    @DisplayName("이름 null 실패")
    @Order(3)
    @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(
                "/validTest",
                stringHttpEntity,
                Void.class
        );

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

    @DisplayName("비밀번호 빈 문자열 실패")
    @Order(4)
    @Test
    public void 비밀번호_빈_문자열_실패() throws Exception {
        UserRequestDto userRequestDto = UserRequestDto.builder()
                .email("whitewise95@gmail.com")
                .age(19)
                .name("짱구")
                .password("")
                .build();

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

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

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

 

400 BAD_REQUEST

전부 BedRequest로 확인된걸 볼 수 있다.

 

콘솔창

이메일 실패

2022-09-01 21:02:50.833  WARN 6864 --- [o-auto-1-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void com.example.examplecollection.valid.controller.ValidTestController.validTest(com.example.examplecollection.valid.dto.UserRequestDto): [Field error in object 'userRequestDto' on field 'email': rejected value [whitewise]; codes [Email.userRequestDto.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userRequestDto.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@e734180,.*]; default message [올바른 형식의 이메일 주소여야 합니다]] ]

 

나이 실패

... [생략]  default message [age],19]; default message [19 이상이어야 합니다]] ]

 

이름 null 실패

... [생략] default message [name]]; default message [널이어서는 안됩니다]] ]

 

빈문자열 비밀번호 실패

... [생략]  default message [password]]; default message [공백일 수 없습니다]] ]

 

200 OK

제약 검증에 만족하는 Data로 테스트를 했습니다.
콘솔창에 아래 데이터가 찍히는 걸 확인 할 수 있습니다.

로직 수행
UserRequestDto{email='whitewise95@gmail.com', password='1234', name='짱구', age=19}
@DisplayName("제약조건 성공")
@Order(5)
@Test
public void 제약조건_성공() throws Exception {
    UserRequestDto userRequestDto = UserRequestDto.builder()
            .email("whitewise95@gmail.com")
            .age(19)
            .name("짱구")
            .password("1234")
            .build();

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

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

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

 

 

전체 코드

 

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

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

github.com

 

 

2부 -  @Validated 예제

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

 

복사했습니다!