@Validation이 필요한 이유

이규훈·2025년 2월 11일

아래는 @Validated의 필요성과 사용법을 쉽게 이해할 수 있도록 한 예제 코드와 부연 설명입니다.


1. @Validated가 필요한 이유

  • 메서드 레벨 검증
    기본적으로 @Valid는 컨트롤러의 파라미터(예, @RequestBody)에서만 동작합니다.
    하지만 서비스나 레포지토리 계층 등 컨트롤러 외의 Spring 빈에서도 유효성 검증을 적용하고 싶다면, Spring이 제공하는 @Validated를 사용합니다.
    @Validated를 클래스 레벨에 붙이면, 해당 빈의 public 메서드 호출 시 AOP 방식으로 유효성 검증이 자동으로 수행됩니다.

  • 그룹(Group) 검증 지원
    @Validated는 validation 그룹을 지정할 수 있기 때문에, 상황에 따라 다른 검증 규칙을 적용할 수 있습니다.
    (예: 생성과 수정 시 서로 다른 제약조건을 적용)

이러한 점 때문에 단순히 @Valid만 사용하는 경우보다 좀 더 다양한 상황에 대응할 수 있어 유용합니다.


2. 예제 코드

예제 1. 컨트롤러에서의 RequestBody 검증

컨트롤러에서는 @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는 컨트롤러 전체에서 메서드 파라미터 검증을 활성화하며, 만약 그룹을 지정한다면 그 그룹에 해당하는 검증만 수행할 수 있습니다.

예제 2. 서비스 계층에서의 메서드 파라미터 검증

컨트롤러 외의 계층에서도 매개변수 검증을 하려면 클래스에 @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)로 잡을 수 있습니다.
    (참고: citeturn0search0, citeturn0search5)

메서드 파라미터 검증을 “활성화한다”는 표현은 단순히 @Valid만 붙이는 것이 아니라, 스프링이 해당 메서드 호출 전에 매개변수 값들을 자동으로 검증하도록(AOP 기반으로) 설정한다는 뜻입니다.

메서드 파라미터 검증을 “활성화한다”?

  1. 컨트롤러와 그 외 계층의 차이

    • 컨트롤러의 경우:
      @RequestBody나 @ModelAttribute와 같이 웹 요청을 처리할 때, 스프링의 ArgumentResolver가 자동으로 @Valid가 붙은 객체를 검증합니다. 그래서 보통 컨트롤러 파라미터에는 @Valid만 있어도 유효성 검사가 잘 작동합니다.
    • 서비스나 기타 스프링 빈의 경우:
      이들 계층에서는 웹 바인딩 과정이 없으므로 @Valid만 붙여서는 자동 검증이 일어나지 않습니다.
      이때 @Validated를 클래스(또는 메서드) 레벨에 붙이면, 스프링이 MethodValidationPostProcessor(AOP 기반)를 통해 메서드 호출 시점에 매개변수를 자동으로 검증하도록 “활성화”할 수 있습니다.
      즉, @Validated가 있어야 서비스 메서드에 전달되는 인자들도 AOP 인터셉터를 통해 제약 조건(@NotNull, @Min 등)이 검사됩니다.
  2. 검증 그룹(Group) 기능 지원
    @Validated는 검증 그룹을 지정할 수 있는 기능을 제공합니다. 예를 들어, 생성 시와 수정 시 서로 다른 검증 규칙을 적용하고 싶을 때 그룹을 지정할 수 있습니다. 반면, @Valid는 기본적으로 그룹 지정 기능이 없어서 이런 세밀한 제어가 어렵습니다.

요약

  • @Valid

    • JSR-303/JSR-380 표준 어노테이션
    • 주로 컨트롤러의 파라미터(예: @RequestBody)에서 데이터 바인딩 시 자동 검증에 사용됨
  • @Validated

    • 스프링 전용 어노테이션
    • 클래스나 메서드에 적용하면, 스프링 AOP를 통해 메서드 호출 전에 매개변수 검증을 “활성화”
    • 서비스 계층 등 웹 바인딩 외의 곳에서도 자동 검증을 수행하며, 검증 그룹 기능도 지원

따라서 단순한 웹 요청 처리에서는 @Valid만으로 충분할 수 있지만, 서비스 등 다른 계층에서도 메서드 파라미터 검증이 필요하거나, 검증 그룹을 사용해야 할 경우에는 @Validated가 필요합니다.


Validation예시

아래 예시를 보면, 같은 DTO를 생성(create)과 수정(update) 시에 재사용할 때, 각 상황마다 요구되는 필드와 제약 조건이 달라질 수 있는데, 이때 검증 그룹(validation group)을 활용하면 한 DTO 내에서 서로 다른 검증 로직을 적용할 수 있습니다.

예를 들어, 사용자 정보를 나타내는 DTO가 있다고 가정해봅니다.

  • 생성(create) 시에는 username과 password, email이 반드시 필요하지만
  • 수정(update) 시에는 id가 반드시 있어야 하고, email은 옵션일 수 있다고 가정합니다.

아래는 이를 위한 예시 코드입니다.


// 검증 그룹을 위한 마커 인터페이스
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 내에서 상황에 따라 다른 검증 로직을 적용할 수 있기 때문에, 코드의 중복을 줄이고 재사용성을 높일 수 있습니다.

profile
개발취준생

0개의 댓글