스프링 부트의 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은 데이터 유효성 검사를 간편하고 체계적으로 수행할 수 있게 해줍니다. 위 어노테이션들을 활용하면 숫자, 문자열, 날짜 등 다양한 데이터 타입에 대해 유연한 검증 규칙을 적용할 수 있습니다. 프로젝트에서는 요구사항에 맞는 어노테이션을 선택하고, 오류 메시지를 사용자 친화적으로 커스터마이징하여 더 나은 사용자 경험을 제공할 수 있습니다.

추가 질문이 있다면 언제든 물어보세요!