Spring Boot에서의 데이터 검증은 사용자의 입력 데이터를 검증하고, 잘못된 입력이 서비스에 영향을 미치는 것을 방지하기 위해 필수적인 기능입니다. 기본적으로 Spring Boot는 Java Bean Validation API와 Hibernate Validator를 사용하며, 간단한 설정과 어노테이션으로 강력한 검증 기능을 제공합니다. 이 글에서는 실무에서 자주 사용되는 데이터 검증 방법과 모범 사례를 다룹니다.
1. Validation 의존성 추가
Spring Boot 프로젝트에서 데이터 검증을 위해서는 spring-boot-starter-validation
의존성을 추가해야 합니다.
Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
2. Controller에서 Validation 사용하기
Spring Boot에서는 @Valid
또는 @Validated
어노테이션을 사용하여 요청 데이터를 검증할 수 있습니다. 실무에서는 DTO(Data Transfer Object)를 활용해 입력 데이터를 구조화하고, 검증 로직을 DTO에 정의하는 방식이 일반적입니다.
예제: 회원 가입 요청 데이터 검증
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class SignupRequest {
@NotBlank(message = "이메일은 필수 입력 값입니다.")
@Email(message = "유효한 이메일 형식이 아닙니다.")
private String email;
@NotBlank(message = "비밀번호는 필수 입력 값입니다.")
@Size(min = 8, message = "비밀번호는 최소 8자리 이상이어야 합니다.")
private String password;
// Getters and Setters
}
Controller 코드
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping("/signup")
public String signup(@Validated @RequestBody SignupRequest request) {
return "회원가입 요청이 정상 처리되었습니다.";
}
}
3. 검증 실패 처리: 예외 처리하기
검증이 실패하면 Spring은 MethodArgumentNotValidException
또는 ConstraintViolationException
을 발생시킵니다. 실무에서는 예외를 처리하여 사용자에게 의미 있는 메시지를 반환하는 것이 중요합니다.
글로벌 예외 처리
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@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 new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
4. Validation 어노테이션 종류
Java Bean Validation에서 제공하는 주요 검증 어노테이션과 사용 예는 다음과 같습니다:
어노테이션 | 설명 | 예제 |
---|---|---|
@NotNull | null을 허용하지 않음 | @NotNull private String name; |
@NotBlank | 공백이나 null을 허용하지 않음 | @NotBlank private String email; |
@Size | 문자열/컬렉션의 크기 검증 | @Size(min = 2, max = 30) private String name; |
@Min/@Max | 숫자의 최소/최대값 검증 | @Min(18) private int age; |
이메일 형식 검증 | @Email private String email; |
|
@Pattern | 정규식을 이용한 문자열 검증 | @Pattern(regexp = "\\d{3}-\\d{3,4}-\\d{4}") private String phone; |
5. 커스텀 Validation 구현하기
기본 제공 어노테이션으로 해결되지 않는 검증 로직이 필요한 경우, 커스텀 Validator를 구현할 수 있습니다.
예제: 비밀번호 복잡도 검증
1) 커스텀 어노테이션 정의
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = PasswordValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPassword {
String message() default "비밀번호는 대문자, 소문자, 숫자, 특수문자를 포함해야 합니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2) Validator 클래스 구현
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
private static final String PASSWORD_PATTERN = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && value.matches(PASSWORD_PATTERN);
}
}
3) DTO에서 사용
import jakarta.validation.constraints.NotBlank;
public class SignupRequest {
@NotBlank(message = "이메일은 필수 입력 값입니다.")
private String email;
@ValidPassword
private String password;
// Getters and Setters
}
6. 서비스 계층에서의 Validation
Controller에서 입력 데이터를 검증한 후, 비즈니스 로직을 수행하기 전에 추가적인 검증이 필요한 경우가 많습니다. 이는 다음과 같은 이유 때문입니다.
- 중복 검증 방지: Controller에서 검증되지 않은 데이터 또는 별도의 검증 로직이 필요한 경우를 처리합니다.
- 도메인 로직 분리: 비즈니스 로직에 가까운 검증은 Service 레이어에서 수행하는 것이 더 적합합니다.
예제: 이메일 중복 확인 로직
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void signup(SignupRequest request) {
if (isEmailDuplicate(request.getEmail())) {
throw new IllegalArgumentException("이미 사용 중인 이메일입니다.");
}
// 회원가입 로직 수행
}
private boolean isEmailDuplicate(String email) {
// 데이터베이스에서 이메일 중복 여부 확인 (예시)
return false;
}
}
Controller에서의 호출
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/signup")
public String signup(@Validated @RequestBody SignupRequest request) {
userService.signup(request);
return "회원가입이 완료되었습니다.";
}
}
7. 실무에서의 Validation 모범 사례
- DTO 활용: Controller에서 입력 데이터를 처리할 때 반드시 DTO를 사용하고, MyBatis Mapper 결과나 데이터베이스와 매핑된 객체를 직접 노출하지 않도록 합니다.
- 서비스 계층 Validation: Controller의 입력 검증 외에도, 비즈니스 로직과 밀접한 데이터 검증은 Service 레이어에서 수행합니다.
- 커스텀 Validator 적극 활용: 기본 어노테이션으로 검증이 부족하면 커스텀 검증 로직을 작성합니다.
'SpringBoot' 카테고리의 다른 글
스프링 빈 자동 등록과 의존성 주입 (1) | 2025.05.01 |
---|---|
싱글톤 패턴과 스프링의 싱글톤 관리 (0) | 2025.04.30 |
Spring Boot: 로깅(Logging) 구현하기 (1) | 2024.11.21 |
Spring Boot: @ExceptionHandler와 @ControllerAdvice를 활용한 예외처리 (0) | 2024.11.20 |
Spring Boot: Form 데이터 처리 (0) | 2024.11.19 |