391 lines
14 KiB
Markdown
391 lines
14 KiB
Markdown
스프링 부트의 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
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-validation</artifactId>
|
|
</dependency>
|
|
```
|
|
|
|
#### 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<String> 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<String> hobbies;
|
|
|
|
public List<String> getHobbies() { return hobbies; }
|
|
public void setHobbies(List<String> 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<String> 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<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
|
return ResponseEntity.badRequest().body(ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 결론
|
|
스프링 부트의 Validation은 데이터 유효성 검사를 간편하고 체계적으로 수행할 수 있게 해줍니다. 위 어노테이션들을 활용하면 숫자, 문자열, 날짜 등 다양한 데이터 타입에 대해 유연한 검증 규칙을 적용할 수 있습니다. 프로젝트에서는 요구사항에 맞는 어노테이션을 선택하고, 오류 메시지를 사용자 친화적으로 커스터마이징하여 더 나은 사용자 경험을 제공할 수 있습니다.
|
|
|
|
추가 질문이 있다면 언제든 물어보세요! |