[SPRING] @Valid 와 @Validated 개념과 차이 (+프로젝트 적용하기)

·2025년 4월 4일

troubleshooting

목록 보기
7/9

1. @Valid

(1) 개념

Java 표준(JSR-380)에 따른 Bean Validation 어노테이션으로, 요청 객체(DTO)의 필드 값 유효성을 검증할 때 사용한다

javax.validation.Valid

  • 주요 목적
    컨트롤러 단에서 요청 데이터의 유효성을 자동으로 검증한다.
    코드 중복 없이, 간결하게 검증 로직 작성 가능하다.

(2) 검증 과정

@Valid annotation을 사용해서 요청 DTO에 있는 필드의 유효성을 자동으로 검사한다. 이때 유효성 조건은 DTO 클래스 내 필드에 부여된 annotation에 따라 달라진다.

public class MemberCreateReqDto {

    @NotBlank(message = "이름은 필수입니다.")
    private String name;
    
    @Email(message = "올바른 이메일 형식이어야 합니다.")
    private String email;
}
@PostMapping("/members")
public ResponseEntity<String> createMember(@RequestBody @Valid MemberCreateReqDto dto) {
    return ResponseEntity.ok("생성 완료");
}

(3) 유효성 검사 실패 시 과정

@PostMapping("/members")
public ResponseEntity<String> createMember(@RequestBody @Valid MemberCreateDto Dto) {
    // ...
}

Spring은 유효성 검사에 실패하면 MethodArgumentNotValidException 예외를 자동으로 발생시킨다.

Spring MVC는 기본적으로 예외를 잡아서 400 Bad Request로 응답해주고,예외 내용을 더 구체적으로 커스터마이징하고 싶다면 @ControllerAdvice로 처리 가능하다.

@ControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<CommonErrorDto> MethodArgumentNotValidExceptionHandler (MethodArgumentNotValidException e) {
        e.printStackTrace();
        return new ResponseEntity<>(new CommonErrorDto(HttpStatus.BAD_REQUEST, e.getFieldError().getDefaultMessage()),HttpStatus.BAD_REQUEST);
    }
}

2. @Validated

(1) 개념

Spring Framework에서 제공하는 유효성 검사용 annotation이다. @Valid와 비슷하지만, 그룹별 유효성 검사를 지원하며, Spring AOP를 활용한 메서드 레벨 검증에도 사용된다.

org.springframework.validation.annotation.Validated

(2) 그룹 검증 지원

public interface CreateGroup {}
public interface UpdateGroup {}

유효성 검사를 상황별로 나누기 위해 위와 같이 그룹 인터페이스를 만들 수 있다.

public class MemberCreateDto {

    @NotBlank(groups = CreateGroup.class, message = "이름은 필수입니다.")
    private String name;

    @Email(groups = {CreateGroup.class, UpdateGroup.class}) 
    private String email;
}    

여러 개 그룹도 가능하다.

@PostMapping("/members")
public ResponseEntity<String> createMember(@RequestBody @Validated(CreateGroup.class) MemberCreateDto dto) {
    // CreateGroup에 해당하는 제약만 검증
    return ResponseEntity.ok("생성 완료");
}

(3) 서비스 계층

@Service
@Validated 
public class MemberService {

    public void create(@NotBlank String name) {
        // name이 null 또는 빈 문자열이면 ConstraintViolationException이 발생한다.
    }
}

@Validated를 클래스에 붙여야 메서드 파라미터 검증이 활성화된다.

(4) 유효성 검사 실패 시 과정

@Valid와 비슷한 과정이다.
유효성 검사에 실패할 경우 어디서 발생했는지에 따라 다른 예외가 자동으로 발생된다.

Spring MVC가 기본적으로 예외를 잡아서 400 Bad Request로 응답해주고,예외 내용을 더 구체적으로 커스터마이징하고 싶다면 @ControllerAdvice로 처리 가능하다.


3. @Valid + @Validated

(1) controller

@PostMapping("/members")
public ResponseEntity<String> createMember(@RequestBody @Valid MemberCreateDto dto) {
    // 필드 유효성 검사 자동 수행됨
}

Controller 단에서는 @Valid, @Validated 둘 다 사용 가능하고,
그룹 검사 필요 없으면 @Valid만 사용해도 충분하다.

@PostMapping("/members")
public ResponseEntity<?> create(@RequestBody @Validated(CreateGroup.class) MemberCreateDto dto) {
    // CreateGroup에 해당하는 조건만 검사됨
}

그룹 검사를 하고 싶다면 @Validated를 사용하고, 그룹 인터페이스를 명시하여 사용하면 된다.

(2) service

@Service
@Validated
public class MemberService {

    public void createByName(@NotBlank String name) {
        // name이 null 또는 공백이면 ConstraintViolationException 발생
    }
 
    public void createByDto(@Valid MemberCreateDto dto) {
        // DTO 내 제약 조건 검사됨 (ex. @NotBlank, @Email)
    }
}

Service 단에서는 반드시 클래스 위에 @Validated가 있어야 메서드 파라미터 유효성 검사 가능하다. (createByName 예시)

이때 파라미터에 @Valid를 붙이면 DTO의 필드 제약까지 검사할 수 있다. @Validated가 있을 경우에만 가능하다.(createByDto 예시)

@Service
public class MemberService {
    
    public void createByDto(@Valid MemberCreateDto dto) {
        // 아무 일도 일어나지 않는다.
    }
}

클래스에 @Validated가 없으면 Spring AOP 기반 검사기가 작동하지 않기 때문에 유효성 검사가 동작하지 않는다.


4. 정리

(1) 공통점

  • 둘 다 Bean Validation 기반의 유효성 검사이다.
  • DTO 필드에 선언된 제약 조건을 검사할 수 있다.

Bean Validation

@NotNull: 해당 값이 null이 아닌지 검증한다.
@NotEmpty: 해당 값이 null이 아니고, 빈 스트링("") 아닌지 검증한다.(빈칸 허용)
@NotBlank: 해당 값이 null이 아니고, 공백이 아닌지 검증한다.(빈칸 허용 X)

@Size: 해당 값이 주어진 값 사이에 해당하는지 검증한다.(String, Collection, Map, Array에도 적용 가능)
@Min: 해당 값이 주어진 값보다 작지 않은지 검증한다.
@Max: 해당 값이 주어진 값보다 크지 않은지 검증한다.
@Pattern: 해당 값이 주어진 패턴과 일치하는지 검증한다.

https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/package-summary.html

(2) 차이점

  • @Valid
    • 그룹 기능 지원 X
    • 메서드 파라미터 검증 지원 X
    • 주로 Controller
  • @Validated
    • 그룹 기능 지원 O
    • 메서드 파라미터 검증 지원 O
    • Controller, Service 모두 가능

(3) 예외 정리

  • 컨트롤러
    • @Valid, @Validated
    • DTO 필드 유효성 실패
      -> MethodArgumentNotValidException 예외 발생
  • 서비스
    • @Validated만 가능
    • 메서드 파라미터 유효성 실패
      -> ConstraintViolationException 예외 발생

5. 프로젝트에 적용하기

implementation 'org.springframework.boot:spring-boot-starter-validation'

build.gradle에 위와 같이 의존성을 추가해야 한다.

(1) 잘못된 적용 예시

  • PlanService.java
@Service
@Transactional
@RequiredArgsConstructor
public class PlanService{
    private final PlanRepository planRepository;

    public PlanResDto planCreate(@Valid PlanCreateReqDto planCreateReqDto) {
    	// ...
    }
}

공부하고 나서 다시 프로젝트를 보니 정말 엉뚱한 곳에 @Valid를 추가한 상태였다..

title에 @NotEmpty을 추가한 상태에서 실행해보니깐
잘 생성된다는 오류가 있다.


(2) 수정

Service단에 있던 @Valid를 삭제하고
Controller단에 @Valid를 추가했다.

  • PlanController.java
@RestController
@RequiredArgsConstructor
@RequestMapping("/plan")
public class PlanController {
    private final PlanService planService;

    @PostMapping
    public ResponseEntity<CommonResDto> planCreate(@Valid @RequestBody PlanCreateReqDto planCreateReqDto) {
        PlanResDto dto = planService.planCreate(planCreateReqDto);
        return new ResponseEntity<>(new CommonResDto(HttpStatus.CREATED, "계획생성이 성공적으로 되었습니다.", dto), HttpStatus.CREATED);
    }
  • PlanCreateReqDto.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PlanCreateReqDto {
	@NotEmpty(message = "title은 필수입니다.")
    private String title;

짠!
title에 @NotEmpty을 붙여주면
에러가 잘 실행된다.













0개의 댓글