개발/Spring
[Spring] @valid
함수형 인간
2025. 3. 27. 17:55
스프링 @Valid 애노테이션
@Valid 애노테이션은 스프링 프레임워크, 특히 스프링 MVC에서 자바 빈 유효성 검사(Java Bean Validation) 표준(JSR-303, JSR-380 등)을 적용하기 위해 사용되는 핵심 애노테이션. 컨트롤러(Controller) 메소드의 파라미터에 이 애노테이션을 붙이면, 해당 파라미터 객체(주로 DTO 또는 Command 객체)에 정의된 유효성 검사 제약 조건들을 자동으로 검증해준다.
@Valid를 사용하는 이유
- 데이터 무결성 보장: 클라이언트로부터 들어오는 데이터(폼 입력, JSON 요청 본문 등)가 애플리케이션에서 요구하는 형식과 제약 조건을 만족하는지 검증하여 데이터의 정합성을 유지.
- 보안 강화: 유효하지 않거나 예상치 못한 형식의 입력 데이터를 사전에 차단하여 SQL 인젝션, 악의적인 스크립트 주입 등의 보안 취약점을 예방.
- 코드 분리 및 가독성 향상: 컨트롤러 메소드 내에서 직접 if문 등으로 복잡하게 유효성 검사 로직을 작성하는 대신, 검증 로직을 해당 데이터 객체(DTO)의 필드 애노테이션으로 분리하여 컨트롤러 코드를 깔끔하게 유지하고 가독성을 높임.
- 표준 기반: 자바 표준 기술(Bean Validation)을 사용하므로 특정 프레임워크에 종속되지 않고 일관된 방식으로 유효성 검사를 구현할 수 있다.
@Valid 사용 방법
1단계: 의존성 추가
@Valid를 사용하려면 자바 빈 유효성 검사 구현체가 필요. 가장 널리 사용되는 구현체는 Hibernate Validator. 스프링 부트 프로젝트의 경우, spring-boot-starter-validation 또는 spring-boot-starter-web 의존성을 추가하면 보통 Hibernate Validator가 자동으로 포함됨.
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
코드 스니펫
// Gradle (build.gradle)
implementation 'org.springframework.boot:spring-boot-starter-validation'
2단계: DTO/Command 객체에 유효성 검사 애노테이션 추가
검증 대상이 될 객체(주로 DTO)의 필드에 표준 Bean Validation 애노테이션들을 사용하여 제약 조건을 정의.
Java
import javax.validation.constraints.*; // JSR-380 (Jakarta Bean Validation)
// 또는 import jakarta.validation.constraints.*; // Jakarta EE 9+
public class UserDto {
@NotBlank(message = "사용자 이름은 필수 입력 항목입니다.") // null, "", " " 모두 허용 안 함
@Size(min = 2, max = 10, message = "사용자 이름은 2자 이상 10자 이하로 입력해주세요.")
private String username;
@NotNull(message = "나이는 필수 입력 항목입니다.")
@Min(value = 1, message = "나이는 1살 이상이어야 합니다.")
private Integer age;
@NotEmpty(message = "이메일은 필수 입력 항목입니다.") // null, "" 허용 안 함
@Email(message = "유효한 이메일 형식이 아닙니다.")
private String email;
// 필요에 따라 @Pattern, @Max, @Past, @Future 등 다양한 애노테이션 사용 가능
}
3단계: 컨트롤러 메소드 파라미터에 @Valid 적용
유효성 검사를 수행할 DTO 파라미터 앞에 @Valid 애노테이션을 붙인다. @ModelAttribute (폼 데이터)와 @RequestBody (JSON/XML 데이터) 모두에 적용할 수 있다.
Java
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.http.ResponseEntity;
@RestController
@RequestMapping("/api/users")
public class UserController {
// JSON 요청 본문을 검증할 때
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody UserDto userDto, BindingResult bindingResult) {
// 1. 유효성 검사 오류 확인
if (bindingResult.hasErrors()) {
// 오류 처리 로직 (예: 오류 메시지를 모아 응답)
StringBuilder errorMsg = new StringBuilder();
for (FieldError error : bindingResult.getFieldErrors()) {
errorMsg.append(error.getField())
.append(": ")
.append(error.getDefaultMessage())
.append("\n");
}
// 400 Bad Request 와 함께 오류 메시지 반환
return ResponseEntity.badRequest().body(errorMsg.toString());
}
// 2. 유효성 검사 통과 시 비즈니스 로직 수행
User savedUser = userService.createUser(userDto);
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}
// 폼 데이터를 검증할 때 (일반적인 웹 MVC)
@PostMapping("/register")
public String registerUser(@Valid @ModelAttribute("user") UserDto userDto, BindingResult bindingResult, Model model) {
// 1. 유효성 검사 오류 확인
if (bindingResult.hasErrors()) {
// 오류가 있으면 다시 회원가입 폼으로 이동 (오류 메시지 포함)
return "user/registerForm"; // 폼 뷰 이름
}
// 2. 유효성 검사 통과 시 비즈니스 로직 수행
userService.register(userDto);
return "redirect:/login"; // 성공 시 로그인 페이지로 리다이렉트
}
}
유효성 검사 오류 처리 (BindingResult / Errors)
- @Valid 애노테이션이 붙은 파라미터 바로 뒤에 BindingResult 또는 Errors 타입의 파라미터를 선언하면, 유효성 검사 결과(오류 정보)가 해당 파라미터에 자동으로 담긴다.
- 중요: BindingResult 파라미터가 @Valid 파라미터 바로 뒤에 오지 않으면, 검증 실패 시 BindException(@ModelAttribute의 경우) 또는 MethodArgumentNotValidException(@RequestBody의 경우) 예외가 발생하고 컨트롤러 메소드가 실행되지 않는다. BindingResult를 사용하면 예외 발생 없이 컨트롤러 내에서 오류를 직접 처리할 수 있다.
- bindingResult.hasErrors(): 오류가 하나라도 있는지 확인합니다.
- bindingResult.getFieldErrors(): 모든 필드 오류 정보를 List<FieldError> 형태로 반환합니다.
- FieldError 객체 주요 메소드:
- getField(): 오류가 발생한 필드명
- getRejectedValue(): 검증에 실패한 실제 입력 값
- getDefaultMessage(): DTO 애노테이션에 정의된 message 속성 값 (오류 메시지)
주요 유효성 검사 애노테이션 (javax.validation.constraints / jakarta.validation.constraints)
- @NotNull: null 값만 허용하지 않음 ("" 이나 " "는 허용)
- @NotEmpty: null과 빈 문자열("")을 허용하지 않음 (" "는 허용). 주로 Collection 이나 Map 에도 사용됨 (비어있는지 검사).
- @NotBlank: null, 빈 문자열(""), 공백 문자열(" ") 모두 허용하지 않음. 주로 String 타입에 사용.
- @Size(min=, max=): 문자열 길이, 배열 크기, 컬렉션 크기가 지정된 범위 내에 있는지 검사.
- @Min(value=): 숫자 값이 지정된 최소값 이상인지 검사.
- @Max(value=): 숫자 값이 지정된 최대값 이하인지 검사.
- @Email: 유효한 이메일 형식인지 검사.
- @Pattern(regexp=): 값이 지정된 정규 표현식과 일치하는지 검사.
- @Positive, @PositiveOrZero: 양수 또는 0 이상인지 검사.
- @Negative, @NegativeOrZero: 음수 또는 0 이하인지 검사.
- @Future, @Past, @FutureOrPresent, @PastOrPresent: 날짜/시간 값이 미래, 과거, 현재 또는 미래/과거인지 검사.
@Valid vs @Validated
- @Valid: JSR 표준 애노테이션. 기본적으로 유효성 검사를 트리거합니다. 그룹(Group) 기능을 직접 지원하지 않는다.
- @Validated: 스프링 프레임워크에서 제공하는 애노테이션. @Valid의 기능을 포함하며, 추가적으로 유효성 검사 그룹을 지정할 수 있는 기능을 제공. 특정 상황(예: 객체 생성 시 검증 규칙과 수정 시 검증 규칙을 다르게 적용)에 유용하게 사용될 수 있다. 서비스 계층 등 컨트롤러가 아닌 다른 계층의 메소드 파라미터 유효성 검사에도 사용할 수 있다.
Java
// 그룹 인터페이스 정의
public interface CreationGroup {}
public interface UpdateGroup {}
public class ItemDto {
@NotNull(groups = UpdateGroup.class) // 수정 시에만 NotNull 검증
private Long id;
@NotBlank(groups = {CreationGroup.class, UpdateGroup.class}) // 생성, 수정 모두 NotBlank 검증
private String itemName;
}
@RestController
public class ItemController {
@PostMapping("/items") // 생성
public void createItem(@Validated(CreationGroup.class) @RequestBody ItemDto itemDto, BindingResult bindingResult) { ... }
@PutMapping("/items/{id}") // 수정
public void updateItem(@PathVariable Long id, @Validated(UpdateGroup.class) @RequestBody ItemDto itemDto, BindingResult bindingResult) { ... }
}
중첩된 유효성 검사 (Nested Validation)
DTO 안에 다른 객체가 포함되어 있고, 그 내부 객체까지 검증하고 싶을 때는 해당 필드에도 @Valid 애노테이션을 붙여주면 된다.
Java
public class OrderDto {
@NotNull
private Long orderId;
@Valid // Address 객체 내부의 필드들도 검증하도록 지정
@NotNull
private Address address;
}
public class Address {
@NotBlank
private String street;
@NotBlank
private String zipCode;
}