@Valid
필드, DTO에 JSR-303, JSR-380 표준 검증 을 적용한다.
DTO에서 @Valid 적용
import jakarta.validation.constraints.*; public class UserDTO { @NotBlank(message = "이름은 필수 입력 값입니다.") private String name; @Email(message = "올바른 이메일 형식이 아닙니다.") private String email; @NotNull(message = "나이는 필수 입력 값입니다.") @Min(value = 18, message = "나이는 18세 이상이어야 합니다.") private Integer age; // Getter, Setter 추가 }Controller에서 @Valid 적용
@RestController @RequestMapping("/users") public class UserController { @PostMapping("/") public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO userDTO) { return ResponseEntity.ok("User created successfully"); } }예외 처리 커스텀
기본적으로 @Valid가 실패하면 Spring Boot는 MethodArgumentNotValidException을 발생시킴 이를 커스텀하여 메시지 커스텀 가능
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage())); return ResponseEntity.badRequest().body(errors); } }Valid에서 사용할 수 있는 검증 어노테이션
1. 공통 어노테이션
- @NotNull: 값이 null이면 안 됨 - @NotEmpty: 값이 null이 아니면서 비어 있으면 안 됨 - @NotBlank: 공백을 포함한 빈 문자열도 허용되지 않음2. 숫자 검증
- @Min(value): 최소값 지정 - @Max(value): 최대값 지정 - @Positive: 양수만 허용 - @Negative: 음수만 허용 - @PositiveOrZero: 0 또는 양수 허용 - @NegativeOrZero: 0 또는 음수 허용문자열 관련 검증
- @Size(min, max): 문자열 길이 제한 - @Pattern(regexp): 정규식 패턴 적용 - @Email: 이메일 형식 검증날짜 관련 검증
- @Future: 미래 날짜만 허용 - @Past: 과거 날짜만 허용 - @FutureOrPresent: 현재 또는 미래 날짜 허용 - @PastOrPresent: 현재 또는 과거 날짜 허용Boolean 관련 검증
- @AssertTrue: 값이 true여야 함 - @AssertFalse: 값이 false여야 함
@Validated
@valid보다 확장된 기능을 제공한다.
- 그룹검증 가능, 메서드 레벨 검증 가능
예시
기본 DTO생성
import jakarta.validation.constraints.*; public class UserDTO { @NotBlank(message = "이름은 필수 입력 값입니다.") private String name; @Email(message = "올바른 이메일 형식이 아닙니다.") private String email; @Min(value = 18, message = "나이는 18세 이상이어야 합니다.") private Integer age; // Getter & Setter }Controller에서 @Validated적용
@RestController @RequestMapping("/users") public class UserController { @PostMapping("/") public ResponseEntity<String> createUser(@Validated @RequestBody UserDTO userDTO) { return ResponseEntity.ok("User created successfully"); } }@Validated의 확장 기능 - 그룹 검증
그룹 검증이 필요한이유
- 회원 가입 시: 이름, 이메일, 나이 필수
- 회원 정보 수정 시: 이름, 이메일만 필수
- 두 상황에서 검증 기준이 다르므로 그룹 검증을 사용하여 다르게 처리할 수 있음
그룹검증 정의
public interface CreateUser {} public interface UpdateUser {}DTO에서 그룹 지정
public class UserDTO { @NotBlank(groups = CreateUser.class, message = "이름은 필수 입력 값입니다.") private String name; @Email(groups = {CreateUser.class, UpdateUser.class}, message = "올바른 이메일 형식이 아닙니다.") private String email; @Min(value = 18, groups = CreateUser.class, message = "나이는 18세 이상이어야 합니다.") private Integer age; }Controller에서 그룹 검증 적용
@PostMapping("/create") public ResponseEntity<String> createUser(@Validated(CreateUser.class) @RequestBody UserDTO userDTO) { return ResponseEntity.ok("User created successfully"); } @PutMapping("/update") public ResponseEntity<String> updateUser(@Validated(UpdateUser.class) @RequestBody UserDTO userDTO) { return ResponseEntity.ok("User updated successfully"); }@Validated의 확장 기능 - 메서드 레벨 검증
메서드 검증이 필요한 이유
- Controller뿐만 아니라 Service 계층에서도 데이터 검증을 수행할 수 있어야 함
- Service에서 잘못된 데이터가 들어오면 예외를 발생시켜야 함
@Validated를 이용한 Service 검증
@Service @Validated public class UserService { public void validateUser(@Valid UserDTO userDTO) { // 비즈니스 로직 수행 System.out.println("Valid User: " + userDTO.getName()); } }Controller에서 호출
@RestController @RequestMapping("/users") public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @PostMapping("/validate") public ResponseEntity<String> validateUser(@RequestBody UserDTO userDTO) { userService.validateUser(userDTO); return ResponseEntity.ok("User validation passed"); } }