Validation
1. Spring MVC Validation
- 이번 글에서는 Spring MVC에서 Validation을 어떻게 설정하고 사용하는지에 대해 알아보겠습니다.
- Spring MVC는 @RequestMapping 메서드에 대한 내장 유효성 검증 기능이 있습니다.
- @RequestMapping 메서드란 Spring MVC에서 HTTP 요청을 처리하는 메서드를 의미합니다.
- 말 그대로 @RequestMapping 어노테이션이 붙은 메서드입니다.
2. Spring MVC Validation 예시
- 세부 내용을 알아보기 전에 Validation을 사용하는 예시를 먼저 살펴보면서 이해를 돕겠습니다.
@PostMapping("/users")
public ResponseEntity<String> registerUser(
@Valid @RequestBody UserRegistrationRequest request) {
// 실제로는 여기서 서비스 로직을 호출하여 사용자를 등록할 것입니다.
return ResponseEntity.ok("User registered successfully: " + request.email());
}
public record UserRegistrationRequest(
@NotBlank(message = "{validation.email.notBlank}")
@Email(message = "{validation.email.invalid}")
String email,
@NotBlank(message = "{validation.password.notBlank}")
@Size(min = 8, max = 20, message = "{validation.password.size}")
@Pattern(regexp = "^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[!@#$%^&*]).+$", message = "{validation.password.pattern}")
String password,
@NotBlank(message = "{validation.password.confirm.notBlank}")
String confirmPassword,
@NotBlank(message = "{validation.name.notBlank}")
String name,
@Past(message = "{validation.birthDate.past}")
LocalDate birthDate) {
}
- 위의 예시에서 @Valid 어노테이션을 사용하여 UserRegistrationRequest 객체에 대한 유효성 검사를 수행합니다.
- UserRegistrationRequest 클래스에 정의된 필드에 대해 유효성 검사를 수행합니다.
- 즉 컨트롤러 메서드 내부에서 직접 입력값 검증을 수행하는 것이 아니라, Spring MVC가 자동으로 검증을 수행합니다.
- 어떻게 이 기능이 작동하는지 알아보겠습니다.
3. 두 가지 유효성 검증 방식
- Spring MVC에서는 두 가지 유효성 검증 방식을 제공합니다.
- 하나는 검증하고 싶은 파라미터에 @Valid 어노테이션을 붙여 검증하는 방식이고 또 다른 하나는 파라미터에 직접 제약 조건을 붙여 검증하는 방식입니다.
- 전자를 **메서드 인자 검증(Method Argument Validation)**이라고 하고, 후자를 **메서드 검증(Method Validation)**이라고 합니다.
- 메서드 인자 검증: @Valid 어노테이션을 사용하여 메서드 인자에 대한 유효성 검증을 수행합니다.
- 메서드 검증: @Constraint 어노테이션(@Min, @NotBlank 등)을 사용하여 메서드 인자 또는 반환값에 대한 유효성 검증을 수행합니다.
- 아래 예시를 통해 구분해보겠습니다.
3.1 두 가지 유효성 방식 예시
3.1.1 메서드 인자 검증(Method Argument Validation)
- 메서드 인자 검증은 아래 조건을 만족해야 합니다.
- 검증하고자 하는 파라미터에 @Valid 또는 @Validated 어노테이션을 붙여야 합니다.
- 파라미터 바로 뒤에 Errors 또는 BindingResult 타입의 파라미터가 없어야 합니다.
- 아규먼트 리졸버가 @ModelAttribute, @RequestBody, 또는 @RequestPart가 붙은 파라미터에 대해서 위 조건을 만족하는 경우에만 유효성 검증을 수행합니다.
@PostMapping("/users")
public ResponseEntity<String> registerUser(
@Valid @RequestBody UserRegistrationRequest request) {
// 실제로는 여기서 서비스 로직을 호출하여 사용자를 등록할 것입니다.
return ResponseEntity.ok("User registered successfully: " + request.email());
}
public record UserRegistrationRequest(
@NotBlank(message = "{validation.email.notBlank}")
@Email(message = "{validation.email.invalid}")
String email,
@NotBlank(message = "{validation.password.notBlank}")
@Size(min = 8, max = 20, message = "{validation.password.size}")
@Pattern(regexp = "^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[!@#$%^&*]).+$", message = "{validation.password.pattern}")
String password,
@NotBlank(message = "{validation.password.confirm.notBlank}")
String confirmPassword,
@NotBlank(message = "{validation.name.notBlank}")
String name,
@Past(message = "{validation.birthDate.past}")
LocalDate birthDate) {
}
- 위 예시가 메서드 인자 검증인 이유는 아래와 같습니다.
- @Valid 어노테이션이 UserRegistrationRequest 파라미터에 붙어있습니다.
- @Valid 어노테이션이 붙은 파라미터 뒤에 Errors 또는 BindingResult 타입의 파라미터가 없습니다.
- @ModelAttribute, @RequestBody, @RequestPart가 붙은 파라미터입니다.
- 따라서 메서드 인자 검증이 수행됩니다.
3.1.2 메서드 검증(Method Validation)
- 메서드 검증은 아래 조건을 만족해야 합니다.
- @Constraint 어노테이션(@Min, @NotBlank 등)이 파라미터에 직접 붙어 있어야 합니다.
- 또는 반환값 검증을 위해 메서드에 @Constraint 어노테이션(@Min, @NotBlank 등)이 직접 붙어 있어야 합니다.
@GetMapping("/users/{id}")
public ResponseEntity<String> getUserById(
@PathVariable @NotNull @Min(value = 1, message = "{validation.id.min}") Long id) {
return ResponseEntity.ok("User found with ID: " + id);
}
@GetMapping("/users/search")
public ResponseEntity<String> searchUsers(
@RequestParam(required = false) @Size(min = 2, message = "{validation.name.size}") String name,
@RequestParam(required = false) @Min(value = 18, message = "{validation.age.min}") Integer age,
@RequestParam(required = false) @Pattern(regexp = "^[MF]$", message = "{validation.gender.pattern}") String gender) {
return ResponseEntity.ok("Users found with criteria: name=" + name + ", age=" + age + ", gender=" + gender);
}
@PostMapping("/users/validate-email")
@NotNull(message = "{validation.response.notNull}")
public ResponseEntity<Boolean> validateEmail(
@RequestParam @NotBlank @Email(message = "{validation.email.invalid}") String email) {
// 이메일 유효성 검사 로직
boolean isValid = email != null && email.contains("@");
return ResponseEntity.ok(isValid);
}
- 위 예시가 메서드 검증인 이유는 아래와 같습니다.
- 첫 번째 메서드에서는 id 파라미터에 @NotNull과 @Min 어노테이션이 직접 붙어 있습니다.
- 두 번째 메서드에서는 각 파라미터에 @Size, @Min, @Pattern 어노테이션이 직접 붙어 있습니다.
- 세 번째 메서드에서는 파라미터에 @NotBlank, @Email 어노테이션이 직접 붙어 있고, 메서드 자체에도 @NotNull 어노테이션이 붙어 있어 반환값도 검증합니다.
- 이러한 경우 메서드 검증이 수행됩니다.
3.2 두 가지 검증 방식이 모두 적용되는 경우
- 메서드 인자 검증 조건과 메서드 검증 조건이 모두 만족되는 경우에는 메서드 검증이 우선적으로 적용됩니다.
@PostMapping("/users")
public ResponseEntity<String> registerUser(
@Valid @NotNull(message = "{validation.request.notNull}") @RequestBody UserRegistrationRequest request) {
// 실제로는 여기서 서비스 로직을 호출하여 사용자를 등록할 것입니다.
return ResponseEntity.ok("User registered successfully: " + request.email());
}
- 위 예시에서는 @Valid와 @NotNull 어노테이션이 모두 붙어 있습니다.
- 따라서 메서드 인자 검증과 메서드 검증 조건을 모두 만족합니다.
- 이런 경우에는 메서드 검증이 우선적으로 적용되어 파라미터가 null인지 먼저 확인하고, null이 아닐 경우 @Valid를 통해 객체 내부 필드의 유효성을 검증합니다.
정보
@Valid는 제약 조건 어노테이션이 아니라, 객체 내부의 중첩된 제약 조건을 위한 것입니다. 객체의 필드에 적용된 제약 조건을 재귀적으로 검증하도록 지시하는 표시자 역할을 합니다. 즉 @Valid 단독으로는 "메서드 유효성 검증"(method validation)을 트리거하지 않고, "메서드 인자 유효성 검증"(method argument validation)을 트리거합니다. 반대로 제약 조건 어노테이션(@Min, @NotBlank 등)은 메서드 인자 유효성 검증을 트리거합니다. 따라서 @Valid와 제약 조건 어노테이션이 함께 사용되면 메서드 인자 유효성 검증이 우선적으로 적용됩니다.
3.3 검증 실패 시 예외
- 두 가지 유효성 검증 방식 모두 검증에 실패하면 예외가 발생하지만 서로 다른 예외가 발생합니다.
- 메서드 인자 검증 실패:
MethodArgumentNotValidException
- 메서드 검증 실패:
HandlerMethodValidationException
- 메서드 인자 검증 실패:
- 따라서 애플리케이션은
MethodArgumentNotValidException
과HandlerMethodValidationException
두 가지 모두를 처리해야 합니다. - 컨트롤러 메서드 시그니처에 따라 두 예외 중 어느 것이든 발생할 수 있기 때문입니다.
- 그러나 이 두 예외는 매우 유사하게 설계되어 있어 거의 동일한 코드로 처리할 수 있습니다.
- 주요 차이점은 전자는 단일 객체에 대한 것이고, 후자는 메서드 파라미터 목록에 대한 것입니다