아래는 @Validated의 필요성과 사용법을 쉽게 이해할 수 있도록 한 예제 코드와 부연 설명입니다.
메서드 레벨 검증
기본적으로 @Valid는 컨트롤러의 파라미터(예, @RequestBody)에서만 동작합니다.
하지만 서비스나 레포지토리 계층 등 컨트롤러 외의 Spring 빈에서도 유효성 검증을 적용하고 싶다면, Spring이 제공하는 @Validated를 사용합니다.
@Validated를 클래스 레벨에 붙이면, 해당 빈의 public 메서드 호출 시 AOP 방식으로 유효성 검증이 자동으로 수행됩니다.
그룹(Group) 검증 지원
@Validated는 validation 그룹을 지정할 수 있기 때문에, 상황에 따라 다른 검증 규칙을 적용할 수 있습니다.
(예: 생성과 수정 시 서로 다른 제약조건을 적용)
이러한 점 때문에 단순히 @Valid만 사용하는 경우보다 좀 더 다양한 상황에 대응할 수 있어 유용합니다.
컨트롤러에서는 @Valid와 함께 @Validated를 클래스 레벨에 붙여서 메서드 파라미터 검증을 활성화할 수 있습니다.
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
@RestController
@RequestMapping("/users")
@Validated // 컨트롤러 내의 메서드 파라미터 검증을 활성화
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(@RequestBody @Valid UserDto userDto) {
// userDto가 유효하면 여기까지 실행됩니다.
return ResponseEntity.ok("User created successfully");
}
}
class UserDto {
@NotBlank(message = "이름은 필수 입력 값입니다.")
private String name;
@Email(message = "이메일 형식이 올바르지 않습니다.")
private String email;
// 생성자, getter, setter
public UserDto() {}
public UserDto(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() { return name; }
public String getEmail() { return email; }
public void setName(String name) { this.name = name; }
public void setEmail(String email) { this.email = email; }
}
설명:
- @Valid는 UserDto 내부의 필드(@NotBlank, @Email) 검증을 실행합니다.
- @Validated는 컨트롤러 전체에서 메서드 파라미터 검증을 활성화하며, 만약 그룹을 지정한다면 그 그룹에 해당하는 검증만 수행할 수 있습니다.
컨트롤러 외의 계층에서도 매개변수 검증을 하려면 클래스에 @Validated를 붙입니다.
아래 예제는 서비스 클래스에서 메서드 파라미터에 대해 유효성 검증을 수행하는 코드입니다.
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.*;
@Service
@Validated // 이 클래스의 public 메서드에 대해 매개변수 검증을 적용
public class UserService {
// userId가 null이 아니고 1 이상의 값이어야 하며, newName은 공백이 없어야 합니다.
public void updateUser(@NotNull @Min(1) Long userId, @NotBlank String newName) {
// update 로직 실행
System.out.println("Updating user " + userId + " with name " + newName);
}
}
설명:
- @Validated가 선언된 클래스의 public 메서드를 호출할 때, Spring AOP가 메서드 매개변수에 설정된 제약조건(@NotNull, @Min, @NotBlank)을 자동으로 검사합니다.
- 검증 실패 시 ConstraintViolationException이 발생하여, 이를 전역 예외 처리(예: @ControllerAdvice)로 잡을 수 있습니다.
(참고: citeturn0search0, citeturn0search5)
메서드 파라미터 검증을 “활성화한다”는 표현은 단순히 @Valid만 붙이는 것이 아니라, 스프링이 해당 메서드 호출 전에 매개변수 값들을 자동으로 검증하도록(AOP 기반으로) 설정한다는 뜻입니다.
컨트롤러와 그 외 계층의 차이
검증 그룹(Group) 기능 지원
@Validated는 검증 그룹을 지정할 수 있는 기능을 제공합니다. 예를 들어, 생성 시와 수정 시 서로 다른 검증 규칙을 적용하고 싶을 때 그룹을 지정할 수 있습니다. 반면, @Valid는 기본적으로 그룹 지정 기능이 없어서 이런 세밀한 제어가 어렵습니다.
@Valid
@Validated
따라서 단순한 웹 요청 처리에서는 @Valid만으로 충분할 수 있지만, 서비스 등 다른 계층에서도 메서드 파라미터 검증이 필요하거나, 검증 그룹을 사용해야 할 경우에는 @Validated가 필요합니다.
아래 예시를 보면, 같은 DTO를 생성(create)과 수정(update) 시에 재사용할 때, 각 상황마다 요구되는 필드와 제약 조건이 달라질 수 있는데, 이때 검증 그룹(validation group)을 활용하면 한 DTO 내에서 서로 다른 검증 로직을 적용할 수 있습니다.
예를 들어, 사용자 정보를 나타내는 DTO가 있다고 가정해봅니다.
아래는 이를 위한 예시 코드입니다.
// 검증 그룹을 위한 마커 인터페이스
public interface CreateGroup {}
public interface UpdateGroup {}
// UserDTO: 동일한 DTO를 생성과 수정에 재사용
public class UserDTO {
// 생성 시에는 username이 필수
@NotBlank(message = "Username은 필수입니다.", groups = CreateGroup.class)
private String username;
// 생성 시에는 password가 필수
@NotBlank(message = "Password는 필수입니다.", groups = CreateGroup.class)
private String password;
// 이메일은 생성과 수정 모두 검증하지만, 그룹을 지정하면 상황에 따라 적용할 수 있음
@Email(message = "올바른 이메일 형식이어야 합니다.", groups = {CreateGroup.class, UpdateGroup.class})
private String email;
// 수정 시에는 id가 반드시 필요
@NotNull(message = "수정 시 id는 필수입니다.", groups = UpdateGroup.class)
private Long id;
// getters, setters 생략
}
컨트롤러에서는 요청에 따라 서로 다른 검증 그룹을 지정하여 DTO를 검증합니다.
@RestController
@RequestMapping("/users")
public class UserController {
// 사용자 생성: CreateGroup에 해당하는 제약 조건이 검증됨
@PostMapping("/create")
public ResponseEntity<String> createUser(
@RequestBody @Validated(CreateGroup.class) UserDTO userDto) {
// 여기서는 username, password, email 검증
// id는 검증하지 않음
// 로직 처리...
return ResponseEntity.ok("User created");
}
// 사용자 수정: UpdateGroup에 해당하는 제약 조건이 검증됨
@PutMapping("/update")
public ResponseEntity<String> updateUser(
@RequestBody @Validated(UpdateGroup.class) UserDTO userDto) {
// 여기서는 id와 email에 대한 검증이 진행됨
// (예를 들어, 수정 시 id가 반드시 있어야 함)
// 로직 처리...
return ResponseEntity.ok("User updated");
}
}
왜 그룹이 필요한가?
보통 하나의 DTO를 오직 한 상황(예: 회원가입 전용)에서 사용한다면 검증 그룹 없이 단순히 @Valid를 사용해도 충분합니다.
그러나 여러 상황(생성, 수정, 일부 업데이트 등)에서 동일한 DTO를 재사용할 경우, 각 상황마다 필수/선택 필드가 달라질 수 있습니다.
이때 그룹을 지정하면, 예를 들어 생성 시에는 CreateGroup에 속한 제약 조건만, 수정 시에는 UpdateGroup에 속한 제약 조건만 실행되도록 할 수 있습니다.
@Validated 사용 시 그룹 지정
@Validated는 Spring에서 제공하는 어노테이션으로, 검증 그룹 기능을 지원합니다.
위 예시처럼 컨트롤러 메서드의 인자에 @Validated(그룹명.class)를 붙이면, 해당 그룹에 속한 제약조건만 검사하게 됩니다.
이는 AOP 기반의 메서드 검증을 통해 이루어지며, 만약 그룹에 해당하는 제약조건에 위배되면 해당 예외(예: MethodArgumentNotValidException)가 발생합니다.
현업에서의 사용
실제로 많은 프로젝트에서는 각각의 상황마다 별도의 DTO를 만드는 경우가 많아 검증 그룹 사용 빈도가 낮을 수 있습니다.
하지만 DTO 재사용이 필요하거나, 같은 DTO를 여러 상황에서 약간씩 다르게 검증해야 하는 경우 검증 그룹은 아주 유용한 기능입니다.
이런 식으로 같은 DTO 내에서 상황에 따라 다른 검증 로직을 적용할 수 있기 때문에, 코드의 중복을 줄이고 재사용성을 높일 수 있습니다.