스프링 부트의 Validation에 대해 설명하는 글을 작성하겠습니다. 스프링 부트에서 Validation은 주로 Bean Validation API(Jakarta Validation, 이전 Hibernate Validator 기반)를 활용하여 요청 데이터의 유효성을 검사하는 데 사용됩니다. 이를 통해 입력값이 요구사항을 충족하는지 확인하고, 잘못된 데이터가 애플리케이션 로직에 들어가는 것을 방지할 수 있습니다. 스프링 부트는 `@Valid`와 같은 어노테이션을 통해 이를 컨트롤러에서 쉽게 적용할 수 있게 지원합니다.
아래에서는 Validation에 사용되는 주요 어노테이션을 표로 정리한 뒤, 각 어노테이션에 대한 설명과 예시를 상세히 다루겠습니다.
---
### 스프링 부트 Validation 주요 어노테이션 표
| 어노테이션 | 설명 |
|-----------------------|----------------------------------------------------------------------------------------|
| `@NotNull` | 값이 null이 아니어야 함 |
| `@NotBlank` | 문자열이 null이 아니고, 공백이 아닌 문자를 포함해야 함 |
| `@NotEmpty` | 컬렉션, 배열, 문자열 등이 null이거나 비어 있지 않아야 함 |
| `@Size` | 문자열, 컬렉션, 배열 등의 크기가 지정된 범위 내에 있어야 함 |
| `@Min` | 숫자가 지정된 최소값 이상이어야 함 |
| `@Max` | 숫자가 지정된 최대값 이하여야 함 |
| `@Pattern` | 값이 지정된 정규 표현식에 맞아야 함 |
| `@Email` | 문자열이 유효한 이메일 형식인지 확인 |
| `@Positive` | 숫자가 0보다 커야 함 |
| `@PositiveOrZero` | 숫자가 0 이상이어야 함 |
| `@Negative` | 숫자가 0보다 작아야 함 |
| `@NegativeOrZero` | 숫자가 0 이하여야 함 |
| `@Future` | 날짜가 현재보다 미래여야 함 |
| `@FutureOrPresent` | 날짜가 현재 또는 미래여야 함 |
| `@Past` | 날짜가 현재보다 과거여야 함 |
| `@PastOrPresent` | 날짜가 현재 또는 과거여야 함 |
| `@AssertTrue` | 값이 true여야 함 (논리 검증용) |
| `@AssertFalse` | 값이 false여야 함 (논리 검증용) |
| `@Digits` | 숫자가 지정된 자릿수(정수, 소수) 범위 내에 있어야 함 |
---
### 각 어노테이션 상세 설명 및 예시
#### 프로젝트 설정
예시를 실행하려면 `pom.xml`에 다음 의존성을 추가해야 합니다:
```xml
org.springframework.boot
spring-boot-starter-validation
```
#### 1. `@NotNull`
- **설명**: 필드가 null이 아니어야 합니다. 공백 문자열("")은 허용됩니다.
- **예시**:
```java
import jakarta.validation.constraints.NotNull;
public class User {
@NotNull(message = "이름은 null일 수 없습니다.")
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
@RestController
public class UserController {
@PostMapping("/user")
public ResponseEntity createUser(@Valid @RequestBody User user) {
return ResponseEntity.ok("유저 생성 성공");
}
}
// 요청: {"name": null} -> 오류: "이름은 null일 수 없습니다."
```
#### 2. `@NotBlank`
- **설명**: 문자열이 null이 아니고, 공백이 아닌 문자를 포함해야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.NotBlank;
public class User {
@NotBlank(message = "이름은 비어 있을 수 없습니다.")
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
// 요청: {"name": ""} -> 오류: "이름은 비어 있을 수 없습니다."
```
#### 3. `@NotEmpty`
- **설명**: 컬렉션, 배열, 문자열 등이 null이거나 비어 있지 않아야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.NotEmpty;
public class User {
@NotEmpty(message = "취미 목록은 비어 있을 수 없습니다.")
private List hobbies;
public List getHobbies() { return hobbies; }
public void setHobbies(List hobbies) { this.hobbies = hobbies; }
}
// 요청: {"hobbies": []} -> 오류: "취미 목록은 비어 있을 수 없습니다."
```
#### 4. `@Size`
- **설명**: 문자열, 컬렉션 등의 크기가 지정된 범위 내에 있어야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.Size;
public class User {
@Size(min = 2, max = 10, message = "이름은 2~10자여야 합니다.")
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
// 요청: {"name": "A"} -> 오류: "이름은 2~10자여야 합니다."
```
#### 5. `@Min`
- **설명**: 숫자가 지정된 최소값 이상이어야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.Min;
public class User {
@Min(value = 18, message = "나이는 18 이상이어야 합니다.")
private int age;
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
// 요청: {"age": 15} -> 오류: "나이는 18 이상이어야 합니다."
```
#### 6. `@Max`
- **설명**: 숫자가 지정된 최대값 이하여야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.Max;
public class User {
@Max(value = 100, message = "나이는 100 이하여야 합니다.")
private int age;
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
// 요청: {"age": 150} -> 오류: "나이는 100 이하여야 합니다."
```
#### 7. `@Pattern`
- **설명**: 값이 지정된 정규 표현식과 일치해야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.Pattern;
public class User {
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "아이디는 영문자와 숫자만 가능합니다.")
private String username;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
}
// 요청: {"username": "user#"} -> 오류: "아이디는 영문자와 숫자만 가능합니다."
```
#### 8. `@Email`
- **설명**: 문자열이 유효한 이메일 형식이어야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.Email;
public class User {
@Email(message = "유효한 이메일 형식이어야 합니다.")
private String email;
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
// 요청: {"email": "invalid-email"} -> 오류: "유효한 이메일 형식이어야 합니다."
```
#### 9. `@Positive`
- **설명**: 숫자가 0보다 커야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.Positive;
public class User {
@Positive(message = "나이는 양수여야 합니다.")
private int age;
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
// 요청: {"age": -5} -> 오류: "나이는 양수여야 합니다."
```
#### 10. `@PositiveOrZero`
- **설명**: 숫자가 0 이상이어야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.PositiveOrZero;
public class User {
@PositiveOrZero(message = "나이는 0 이상이어야 합니다.")
private int age;
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
// 요청: {"age": -1} -> 오류: "나이는 0 이상이어야 합니다."
```
#### 11. `@Negative`
- **설명**: 숫자가 0보다 작아야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.Negative;
public class User {
@Negative(message = "값은 음수여야 합니다.")
private int value;
public int getValue() { return value; }
public void setValue(int value) { this.value = value; }
}
// 요청: {"value": 5} -> 오류: "값은 음수여야 합니다."
```
#### 12. `@NegativeOrZero`
- **설명**: 숫자가 0 이하여야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.NegativeOrZero;
public class User {
@NegativeOrZero(message = "값은 0 이하여야 합니다.")
private int value;
public int getValue() { return value; }
public void setValue(int value) { this.value = value; }
}
// 요청: {"value": 1} -> 오류: "값은 0 이하여야 합니다."
```
#### 13. `@Future`
- **설명**: 날짜가 현재보다 미래여야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.Future;
public class Event {
@Future(message = "이벤트 날짜는 미래여야 합니다.")
private LocalDate eventDate;
public LocalDate getEventDate() { return eventDate; }
public void setEventDate(LocalDate eventDate) { this.eventDate = eventDate; }
}
// 요청: {"eventDate": "2023-01-01"} -> 오류: "이벤트 날짜는 미래여야 합니다."
```
#### 14. `@FutureOrPresent`
- **설명**: 날짜가 현재 또는 미래여야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.FutureOrPresent;
public class Event {
@FutureOrPresent(message = "이벤트 날짜는 현재 또는 미래여야 합니다.")
private LocalDate eventDate;
public LocalDate getEventDate() { return eventDate; }
public void setEventDate(LocalDate eventDate) { this.eventDate = eventDate; }
}
// 요청: {"eventDate": "2023-01-01"} -> 오류: "이벤트 날짜는 현재 또는 미래여야 합니다."
```
#### 15. `@Past`
- **설명**: 날짜가 현재보다 과거여야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.Past;
public class User {
@Past(message = "생일은 과거여야 합니다.")
private LocalDate birthDate;
public LocalDate getBirthDate() { return birthDate; }
public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
}
// 요청: {"birthDate": "2026-01-01"} -> 오류: "생일은 과거여야 합니다."
```
#### 16. `@PastOrPresent`
- **설명**: 날짜가 현재 또는 과거여야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.PastOrPresent;
public class User {
@PastOrPresent(message = "생일은 현재 또는 과거여야 합니다.")
private LocalDate birthDate;
public LocalDate getBirthDate() { return birthDate; }
public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
}
// 요청: {"birthDate": "2026-01-01"} -> 오류: "생일은 현재 또는 과거여야 합니다."
```
#### 17. `@AssertTrue`
- **설명**: 값이 true여야 합니다. 논리 검증에 유용합니다.
- **예시**:
```java
import jakarta.validation.constraints.AssertTrue;
public class User {
private boolean active;
@AssertTrue(message = "사용자는 활성화 상태여야 합니다.")
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }
}
// 요청: {"active": false} -> 오류: "사용자는 활성화 상태여야 합니다."
```
#### 18. `@AssertFalse`
- **설명**: 값이 false여야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.AssertFalse;
public class User {
private boolean deleted;
@AssertFalse(message = "사용자는 삭제 상태일 수 없습니다.")
public boolean isDeleted() { return deleted; }
public void setDeleted(boolean deleted) { this.deleted = deleted; }
}
// 요청: {"deleted": true} -> 오류: "사용자는 삭제 상태일 수 없습니다."
```
#### 19. `@Digits`
- **설명**: 숫자의 정수 및 소수 자릿수가 지정된 범위 내에 있어야 합니다.
- **예시**:
```java
import jakarta.validation.constraints.Digits;
public class Product {
@Digits(integer = 3, fraction = 2, message = "가격은 정수 3자리, 소수 2자리여야 합니다.")
private BigDecimal price;
public BigDecimal getPrice() { return price; }
public void setPrice(BigDecimal price) { this.price = price; }
}
// 요청: {"price": 1234.567} -> 오류: "가격은 정수 3자리, 소수 2자리여야 합니다."
```
---
### 오류 처리 예시
Validation 오류를 처리하려면 `BindingResult`를 사용하거나, 전역 예외 처리를 설정할 수 있습니다:
```java
@RestController
public class UserController {
@PostMapping("/user")
public ResponseEntity createUser(@Valid @RequestBody User user, BindingResult result) {
if (result.hasErrors()) {
return ResponseEntity.badRequest().body(result.getAllErrors().get(0).getDefaultMessage());
}
return ResponseEntity.ok("유저 생성 성공");
}
}
```
#### 전역 예외 처리
```java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex) {
return ResponseEntity.badRequest().body(ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
}
}
```
---
### 결론
스프링 부트의 Validation은 데이터 유효성 검사를 간편하고 체계적으로 수행할 수 있게 해줍니다. 위 어노테이션들을 활용하면 숫자, 문자열, 날짜 등 다양한 데이터 타입에 대해 유연한 검증 규칙을 적용할 수 있습니다. 프로젝트에서는 요구사항에 맞는 어노테이션을 선택하고, 오류 메시지를 사용자 친화적으로 커스터마이징하여 더 나은 사용자 경험을 제공할 수 있습니다.
추가 질문이 있다면 언제든 물어보세요!