Validation이란?
시스템이 미리 정의한 사양(specification)에 부합하고 있는지 검증하는 것.
즉, 입력된 값이 올바른지 확인하는 과정을 말한다.
Validation의 역할
- 검증을 통해 적절한 메시지를 유저에게 보여주어야 한다.
- 검증 오류로 인해 정상적인 동작을 하지 못하는 경우는 없어야 한다.
- 사용자가 입력한 데이터는 유지된 상태여야 한다.
Validation을 사용하는 이유
- 사용자의 실수로 잘못된 값이 들어올 수 있어서
- 입력값이 이상하면 서버까지 망가질 수 있어서
- 보안상 위험을 막기 위해서
- 시스템이 안정적으로 동작하려면 정확한 값이 필요해서
Validation의 종류
| 설명 | 장점 | 단점 | 예시 | |
| 프론트 검증 | 사용자 입력값을 화면에서 즉시 확인 | 사용자 경험(UX) 향상 | 사용자가 우회 가능 → 보안 취약 |
비밀번호에 특수문자 포함 여부 확인 |
| 서버 검증 |
API 호출 시 백엔드에서 검증 | 보안 강화논리 검증 가능 | UX 저하 (요청 후 오류 알림) |
회원가입 시 중복 이메일 확인 |
| DB 검증 |
DB 레벨에서 제약조건으로 검증 (NOT NULL, DEFAULT, UNIQUE 등) | 데이터 무결성 보장최종 방어선 | 오류 발생 시 복구 어려움 UX 관여 불가 |
이메일 칼럼에 NOT NULL 설정 |
Spring Validation 적용을 위한 준비
build.gradle에 아래 의존성을 추가해야 한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
Bean Validation 애너테이션
Validation은 Annotation을 적용시키는 것 만으로 아주 쉽게 적용할 수 있다. 이를 통해 Controller에 개발자가 기본적인 검증 로직을 작성할 필요가 없어졌다.
| 주로 사용하는 애너테이션 | 설명 | 자주 쓰이는 곳 |
| @NotBlank | null, 빈 문자열, 공백 전부 허용 안 함 | String (아이디, 이름 등) |
| @Size(min, max) | 문자열/컬렉션 길이 제한 | 비밀번호, 닉네임 등 |
| 이메일 형식인지 검사 | 이메일 | |
| @Pattern | 정규식으로 형식 검사 | 전화번호, 코드 등 |
| @Min / @Max | 숫자 최소값 / 최대값 지정 | 나이, 수량 등 |
| @Positive | 양수만 허용 | 금액, 수량 등 |
| @NotNull | null만 안 됨 (빈 문자열은 허용됨) | 숫자, Boolean, 날짜 등 |
public class UserDto {
@NotBlank(message = "이름은 필수 입력입니다.")
private String name;
@Size(min = 6, max = 20, message = "비밀번호는 6자 이상 20자 이하로 입력해주세요.")
private String password;
}
※ Spring의 Bean Validation은 Default로 제공하는 Message들이 존재하고 임의로 수정할 수 있다.
꼭 message를 설정해야 하는 것은 아니지만, 메시지를 직접 설정하면 사용자에게 더 친절한 안내할 수 있다.
@Valid vs @Validated 차이점
@Valid, @Validated는 Bean Validation Annotation이 있으면 검증을 수행한다.
| @Valid | @Validated | |
| 제공처 | Java 표준 (javax.validation) | Spring 전용 (org.springframework.validation) |
| 주요 용도 | 단순 객체 검증 (Controller에서 DTO 검증) | 그룹 검증, Service 계층에서도 검증 가능 |
| 그룹 지정 | ❌ 불가능 | ✅ 가능 (groups = ...) |
| 예외 종류 | MethodArgumentNotValidException | ConstraintViolationException |
| 중첩 객체 검증 | ✅ 가능 | ✅ 가능 |
public ResponseEntity<?> createUser(@Valid @RequestBody UserDto dto) {
...
}
Validation 예외 처리
Validation 예외가 발생하면, Spring은 기본적으로 400 Bad Request + 간단한 에러 메시지를 응답으로 보내고 콘솔에는 예외 로그를 출력한다.
하지만 이런 방식은 사용자에게 불친절하고, 에러 정보를 잘 활용하지 못한다. 따라서, 사용자에게 친절하고 명확한 메시지를 제공하고, 서버에 더 자세한 로그를 남기기 위해 BindingResult나 @ControllerAdvice를 사용해 직접 예외를 처리해 주는 게 좋다.
1) BindingResult 사용하기
Validation Error가 발생하면 FieldError, ObjectError를 생성하여 BindingResult에 담아주며, 이를 통해 예외를 처리할 수 있다.
@PostMapping("/users")
public ResponseEntity<?> createUser(@Valid @RequestBody UserDto dto, BindingResult bindingResult) {
// 1. 유효성 검사 실패 시 처리
if (bindingResult.hasErrors()) {
// 필드별 오류 메시지 수집
Map<String, String> errors = new HashMap<>();
bindingResult.getFieldErrors().forEach(error -> {
errors.put(error.getField(), error.getDefaultMessage());
});
// 오류 내용을 포함한 응답 반환
return ResponseEntity.badRequest().body(errors);
}
// 2. 정상 로직 수행
return ResponseEntity.ok("회원가입 성공!");
}
2) GlobalExceptionHandler 사용하기
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);
}
groups
Bean Validation의 groups 속성은 다양한 유효성 검사 시나리오를 정의할 때 사용된다. 동일한 객체에 대한 검증을 상황에 따라 다르게 적용하고 싶을 때 groups를 활용할 수 있다.
public class UserDto {
@NotBlank(message = "이름은 필수입니다.", groups = JoinGroup.class)
private String name;
@NotBlank(message = "비밀번호는 필수입니다.", groups = JoinGroup.class)
@Size(min = 6, message = "비밀번호는 6자 이상이어야 합니다.", groups = JoinGroup.class)
private String password;
@NotBlank(message = "이메일은 필수입니다.", groups = {JoinGroup.class, LoginGroup.class})
private String email;
}
// 회원가입 검증
@PostMapping("/join")
public ResponseEntity<?> join(@Validated(JoinGroup.class) @RequestBody UserDto dto, BindingResult bindingResult) {
...
}
// 로그인 검증
@PostMapping("/login")
public ResponseEntity<?> login(@Validated(LoginGroup.class) @RequestBody UserDto dto, BindingResult bindingResult) {
...
}
※ groups 사용 vs DTO 분리
groups는 상황별 유효성 검증이 가능하다는 장점이 있지만,
실무에서는 유지보수성과 명확성 때문에 DTO 자체를 분리해서 사용하는 게 적절하다.
| groups 사용 | DTO 분리 | |
| 개념 | 하나의 DTO에 상황별 유효성 검증을 그룹으로 나눠서 적용 | 저장용 DTO, 수정용 DTO 등으로 아예 클래스를 분리 |
| 장점 | 코드 중복을 줄일 수 있음 | 명확한 역할 분리, 가독성 ↑, 유지보수 쉬움 |
| 단점 | 가독성 ↓, 복잡도 ↑, 실무에선 거의 안 씀 | 중복되는 필드가 생길 수 있음 |
| 실무 적용 | 거의 사용 X (간단한 프로젝트 제외) | ✅ 실무에서는 대부분 이 방법 사용 |
| 권장 네이밍 | - | SaveRequestDto, UpdateRequestDto 등 역할 기반 명명 |
'Spring' 카테고리의 다른 글
| [Spring] JPA 시작하기 (0) | 2025.06.04 |
|---|---|
| [Spring] 쿠키 / 세션 / 토큰 (0) | 2025.05.17 |
| [Spring] Spring Bean 등록과 의존성 주입 (0) | 2025.05.15 |
| [Spring] 예외처리 (1) | 2025.05.14 |
| [Spring] JDBC 시작하기 (0) | 2025.05.14 |