2025. 9. 2. 20:12ㆍBackend/Spring
개요
프로젝트를 진행을 하면서 Code Rabbit 이 프로젝트에서 지적을 해준 부분이 @Validated에 대해서 지적을 해주었다.
이번 기회에 @Valid와 @Validated에 대해서 자세하게 비교를 해보고자 한다.
@Valid와 @Validated의 단순 비교
| 어노테이션 | 클래스 레벨 (@RestController 등) |
파라미터 레벨 (@RequestBody 등)
|
| @Validated | (O) 동작함: 클래스 내 메소드의 개별 파라미터 (@RequestParam 등) 검증 기능을 활성화시킴. |
(O) 동작함: DTO 검증 + 그룹 기능 사용 가능.
|
| @Valid | (X) 동작 안 함: 아무런 효과 없음. |
(O) 동작함: DTO에 대한 기본 유효성 검사 수행 (그룹 기능 없음).
|
단순한 비교는 이러하다
이제 예시를 보면서 사용 방법에 대해서 비교를 해볼까 한다.
공통된 사용 방법
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
@Valid와 @Validated을 사용하기 위해서는 프로젝트에 맞게 spring-boot-starter-validtion을 추가를 해야 한다.
@Valid의 사용 방법
// UserCreateRequest.java
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class UserCreateRequest {
@NotBlank(message = "사용자 이름은 필수 입력 항목입니다.")
private String username;
@NotBlank(message = "이메일은 필수 입력 항목입니다.")
@Email(message = "유효한 이메일 형식이 아닙니다.")
private String email;
@NotNull(message = "나이는 필수 입력 항목입니다.")
@Min(value = 19, message = "19세 이상만 가입할 수 있습니다.")
private Integer age;
// Getter, Setter, 생성자 등
}
일단 DTO를 만들고 각 필드에 유효성 검증 어노테이션을 추가를 한다.
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(@Valid @RequestBody UserCreateRequest request) {
// 유효성 검증을 통과한 경우에만 이 로직이 실행됩니다.
// request 객체는 안전하게 사용할 수 있습니다.
System.out.println("사용자 생성 성공: " + request.getUsername());
return ResponseEntity.ok("사용자 생성이 완료되었습니다.");
}
}
API에 해당 @RequestBody 가 있는 곳에 @Valid 어노테이션을 추가를 하여 사용을 할 수 있다.
@Valid 어노테이션을 추가를 한 곳에 @Validated로 변경하여 사용 또한 가능하다.
@Validated의 사용법 1
위에 적힌 내용처럼 @Valid를 사용한 부분에 @Validated로 변경하여 사용 가능하지만, 추가적인 기능을 소개를 해볼까 한다.
일단 각 우효성 검증 그룹을 위한 인터페이스를 정의한다.
// ValidationGroups.java
public interface ValidationGroups {
interface OnCreate {}
interface OnUpdate {}
}
// UserDto.java
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
public class UserDto {
@Null(groups = ValidationGroups.OnCreate.class, message = "생성 시에는 id를 지정할 수 없습니다.")
@NotNull(groups = ValidationGroups.OnUpdate.class, message = "수정 시에는 id가 반드시 필요합니다.")
private Long id;
@NotBlank(groups = {ValidationGroups.OnCreate.class}, message = "사용자 이름은 필수입니다.")
private String username;
@Email(groups = {ValidationGroups.OnCreate.class, ValidationGroups.OnUpdate.class}, message = "유효한 이메일 형식이 아닙니다.")
@NotBlank(groups = {ValidationGroups.OnCreate.class}, message = "이메일은 필수입니다.")
private String email;
// Getters and Setters
}
위처럼 각 ValidationGroups을 설정하여 하나의 DTO로 여려 DTO의 역할을 할 수 있다.
// UserController.java
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
// 사용자 생성 API
@PostMapping
public ResponseEntity<UserDto> createUser(@Validated(ValidationGroups.OnCreate.class) @RequestBody UserDto userDto) {
// 비즈니스 로직 처리
return ResponseEntity.ok(userDto);
}
// 사용자 정보 수정 API
@PutMapping("/{id}")
public ResponseEntity<UserDto> updateUser(@PathVariable Long id, @Validated(ValidationGroups.OnUpdate.class) @RequestBody UserDto userDto) {
userDto.setId(id);
// 비즈니스 로직 처리
return ResponseEntity.ok(userDto);
}
}
그래서 메서드 부분에서 사용하는 모습을 보면
@Validated(ValidationGroups.OnCreate.class)
@Validated(ValidationGroups.OnUpdate.class)
위 2개처럼 각각 나눠서 사용하는 모습을 볼 수 있다.
그리고 한 가지 방법이 더 있다.
위의 방법을 보면 @RequestBody에 대한 내용만 설명이 된 것을 확인할 수 있다.
그렇다면 @RequestParam이나 @PathVariable 같은 경우는 검증을 어떻게 할까?
이것을 @Validated로 사용할 수 있다.
@Validated 어노테이션은 메서드 파라미터뿐만 아니라, 클래스에 붙일 수 있는 어노테이션이다.
그렇다면 @RequestParam이나 @PathVariable에서는 어떻게 사용하는지 예시를 같이 보겠다.
@Validated의 사용법 2
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
@RestController
@RequestMapping("/api")
@Validated // *** 클래스 레벨에 @Validated를 반드시 추가해야 합니다! ***
public class UserController {
@GetMapping("/users-by-level")
public ResponseEntity<String> getUsersByLevel(
@RequestParam @Min(value = 1, message = "레벨은 1 이상이어야 합니다.")
@Max(value = 99, message = "레벨은 99 이하여야 합니다.") Integer level) {
// 유효성 검증을 통과하면 로직 실행
return ResponseEntity.ok(level + " 레벨 사용자 목록입니다.");
}
}
@RequestParam 이나 @PathVariable 에 대해서 유효성 검증 어노테이션을 붙인 후
class 레벨에 @Validated 어노테이션을 추가를 해서 사용할 수 있다.
그렇다면 Exception의 차이는 어떻게 될까?
@Valid와 @Validated의 Exception 발생 차이
@Valid 어노테이션
- 발생 예외: MethodArgumentNotValidException
- 동작 원리: 주로 @RequestBody, @ModelAttribute와 함께 사용되며, Spring MVC가 컨트롤러 메서드의 인자를 준비하는 과정에서 검증을 수행. 만약 검증에 실패하면, 바인딩된 결과(BindingResult)와 함께 이 예외를 발생.
- 특징:
- 어노테이션 자체(javax.validation.Valid)는 Java 표준 기술
- Spring 컨트롤러에서 자동으로 검증을 수행하고 예외를 던지는 것은 Spring Framework의 고유 기능.
@Validated 어노테이션
- 발생 예외:
- @RequestBody에 사용 시: @Valid와 동일하게 MethodArgumentNotValidException 발생
- @RequestParam, @PathVariable 등 개별 파라미터 검증 시: ConstraintViolationException 발생
- 동작 원리: 클래스 레벨에 @Validated가 붙으면, Spring은 해당 클래스에 AOP 프록시를 적용. 이 프록시가 메서드 호출을 가로채어 파라미터에 붙은 제약 조건(@Min, @Max 등)을 검증함. 이 과정에서 실패하면 ConstraintViolationException이 발생.
- 특징:
- org.springframework.validation.annotation.Validated는 Spring 프레임워크에 종속적인 어노테이션
- AOP를 기반으로 동작하여 메소드 호출 전반에 걸쳐 유효성 검사를 적용 가능.
마무리
https://strnetwork.tistory.com/56
[Spring] 프로젝트 하면서 느낀 아쉬운점 & 배운점 등
뉴스피드 프로젝트 SA 7조 PK7(PRIMARY KEY 7) | NotionS.A. 내용teamsparta.notion.site 뉴스피드 프로젝트 Github GitHub - PK7NewsFeed/NewsFeed: [내배캠] 뉴스피드 프로젝트[내배캠] 뉴스피드 프로젝트. Contribute to PK
strnetwork.tistory.com
옛날에 적었던 내용이 또 틀렸다.
이게 맨날 정확하게 찾아보지 않으면 틀리는것이 이번에는 정확하게 조사를 하여 정리를 하게 되는 계기가 되었다.
현재 하는 프로젝트에서 매번 배우는 것이 많은 것을 보니까 매번 발전하는 것이 느껴져서 좋다.
정보를 찾아보면서 @Valid와 @Validated 의 차이를 보니 거의 @Validated를 상위호환이라고 생각하면서 사용해도 되겠다는 생각이 들었다.
일관성을 위해서 추후 프로젝트를 조금씩 리펙토링을 진행을 할 때 @Validated로 변경을 하는 과정을 거쳐야겠다.
'Backend > Spring' 카테고리의 다른 글
| [Spring] @JsonCreator 와 @JsonProperty 을 사용해야하나? (3) | 2025.08.28 |
|---|---|
| [Spring] 분산락에서 발생하는 레이스 컨디션을 해결..?.part1 (1) | 2025.04.03 |
| [Spring] 트러블 슈팅 @RequestParam name없으면 생기는 문제 (0) | 2025.03.21 |
| [Spring] OutSourcing 프로젝트를 마치며.. (1) | 2025.03.07 |
| [Spring] 테스트 코드를 작성을 해보자. (0) | 2025.02.27 |