@Valid vs @Validated 정리

Jeongkuk Seo
sjk5766
Published in
6 min readFeb 9, 2023

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

1. @Valid는 자바 표준 스펙이고 @Validated는 스프링에서 제공하는 어노테이션이다.
2. @Validated를 통해 그룹 유효성 검사나 Controller가 아닌 다른 계층에서
유효성 검증 가능
3. @ValidMethodArgumentNotValidException 예외를
@ValidatedConstraintViolationException 예외를 발생시킨다.

Valid는 언제 어떻게 사용하나

Valid 어노테이션은 주로 request body를 검증하는데 많이 사용된다.
아래와 같이 User를 추가하는 간단한 Request dto가 있고 NotNull, NotBlank 등의 유효성 검사 어노테이션을 적용했다.

public class UserRequest {
@Email
private String email;

@NotBlank
private String password;

@NotNull
private Address address;

// 생성자 및 getter 함수
}

Address는 값 객체로 아래와 같이 구성했다.

public class Address {
@NotBlank
private String city;

@NotBlank
private String zipCode;

// 생성자 및 getter 함수
}

Controller 내의 User 회원가입 함수(signUp) 파라미터 부분에 Valid 어노테이션을 적어야 기능이 정상 작동한다

@PostMapping()
public String signUp(@Valid @RequestBody UserRequest request) {
return "ok";
}

이 때 실제로 동작을 시켜보면 Address 멤버 변수가 가진 city, zipCode에 대한 유효성 검증은 실행되지 않는다. 즉 아래와 같은 요청이 예외 처리되지 않고 정상적으로 들어온다.

{
"email": "test@gmail.com",
"password": "password",
"address": {
"city": "", // 빈 문자열
"zipCode": null // null
}
}

위와 같이 nested dto를 검증하려면 Address에 NotNull 대신 Valid 어노테이션을 붙여야 정상적으로 동작한다.

public class UserRequest {
@Email
private String email;

@NotBlank
private String password;

@Valid // @NotNull 대신 @Valid를 사용해야함
private Address address;

// 생성자 및 getter 함수
}

Validated는 언제 어떻게 사용하나

검증할 입력 값에는 크게 3가지 유형이 있는데 아래와 같다.

1. query string.    ex) /users?teamId=1
2. query parameter. ex) /users/1
3. request body

request body의 경우 위와 같이 Valid 어노테이션으로 처리하면 되고 만약
쿼리 스트링이나 파라미터를 검증해야 하는 경우는 어떻게 해야 할까?

쿼리 파라미터로 특정 사용자를 조회하는 API가 있다고 가정하자. id는 보통 db의 auto_increment로 1부터 시작하니 이걸 검증한다고 하면, 아래와 같이 Validated 어노테이션을 클래스 레벨에 선언 후 유효성 검사하는 어노테이션을 추가하면 된다.

@RestController
@RequestMapping("/users")
@Validated
public class UserController {
.
.

@GetMapping("/{id}")
public String find(@PathVariable @Min(1) Long id) {
return "ok";
}
}

만약 class 레벨에 Validated 어노테이션이 없다면 Min어노테이션은 동작하지 않는다.

어떠한 이유로 요청을 controller가 아닌 service에서 검증한다고 가정해보자. Service class 단에 Validated 어노테이션을 선언하고 파라미터에 Valid 어노테이션을 적용하면 된다.

@Transactional(readOnly = true)
@Validated // Validated 어노테이션 추가
@Service
public class UserService {
.
.

@Transactional
public void signUp(@Valid UserRequest request) { // 파라미터에 Valid 추가
// 회원가입 로직
}
}

Validated, Valid 방식의 큰 차이점 중 하나로는 그룹 유효성 검사인데 아무리 봐도 이걸 내가 직접 쓸 일이 없을 것 같아 정리하진 않겠다. 궁금하다면
레퍼런스에 적은 링크를 통해 볼 수 있다.

예외 처리

만약 Valid 방법을 선택했는데 입력 값 검증 과정에서 예외가 발생한다면 이는 MethodArgumentNotValidException 예외를 발생시키고 Exception Handler로 핸들링 할 수 있다.

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseMessage<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return ResponseMessage.badRequest("");
}

만약 Validated 방법을 사용했다면 ConstraintViolationException 예외가 발생하고 아래와 같이 처리할 수 있다.

@ExceptionHandler(ConstraintViolationException.class)
public ResponseMessage<String> handleMethodConstraintViolationException(ConstraintViolationException e) {
return ResponseMessage.badRequest("");
}

내 경우 ConstraintViolationException 예외가 잡히지 않아 30분을 소비했는데 import 문제였다. IntelliJ에 복붙했더니 hibernate가 자동으로 import 되서 예외 처리가 되지 않았다. 😇😇😇

레퍼런스

--

--