Files
java-examples/docs/validation.md

14 KiB

스프링 부트의 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에 다음 의존성을 추가해야 합니다:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

1. @NotNull

  • 설명: 필드가 null이 아니어야 합니다. 공백 문자열("")은 허용됩니다.
  • 예시:
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이 아니고, 공백이 아닌 문자를 포함해야 합니다.
  • 예시:
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이거나 비어 있지 않아야 합니다.
  • 예시:
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

  • 설명: 문자열, 컬렉션 등의 크기가 지정된 범위 내에 있어야 합니다.
  • 예시:
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

  • 설명: 숫자가 지정된 최소값 이상이어야 합니다.
  • 예시:
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

  • 설명: 숫자가 지정된 최대값 이하여야 합니다.
  • 예시:
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

  • 설명: 값이 지정된 정규 표현식과 일치해야 합니다.
  • 예시:
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

  • 설명: 문자열이 유효한 이메일 형식이어야 합니다.
  • 예시:
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보다 커야 합니다.
  • 예시:
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 이상이어야 합니다.
  • 예시:
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보다 작아야 합니다.
  • 예시:
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 이하여야 합니다.
  • 예시:
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

  • 설명: 날짜가 현재보다 미래여야 합니다.
  • 예시:
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

  • 설명: 날짜가 현재 또는 미래여야 합니다.
  • 예시:
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

  • 설명: 날짜가 현재보다 과거여야 합니다.
  • 예시:
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

  • 설명: 날짜가 현재 또는 과거여야 합니다.
  • 예시:
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여야 합니다. 논리 검증에 유용합니다.
  • 예시:
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여야 합니다.
  • 예시:
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

  • 설명: 숫자의 정수 및 소수 자릿수가 지정된 범위 내에 있어야 합니다.
  • 예시:
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를 사용하거나, 전역 예외 처리를 설정할 수 있습니다:

@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("유저 생성 성공");
    }
}

전역 예외 처리

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
        return ResponseEntity.badRequest().body(ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
    }
}

결론

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

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