[TIL] Spring Boot Validation

냠냠빈·2024년 12월 24일

✨ Spring Boot Validation: 개발자의 친구, 데이터의 수문장

Spring Boot로 개발하다 보면 사용자로부터 입력받은 데이터가 항상 신뢰할 수 있는 형태로 들어오지 않는다는 걸 알게 됩니다. 여기서 필요한 것이 바로 "Validation"입니다. Spring Boot에서 Validation은 사용자가 입력한 데이터를 검증하는 중요한 기능입니다. 이를 통해 비즈니스 로직 이전에 데이터의 무결성을 보장할 수 있습니다.


🧐 Validation이란 무엇인가요?

Validation은 입력 데이터가 유효한지 검사하는 과정을 의미합니다.

예를 들어, 회원가입 폼에서 이메일 형식이 맞는지 확인하거나, 나이가 음수가 아닌지 검사하는 과정을 떠올리면 됩니다. 이러한 Validation은 서버에서 한 번 더 데이터를 검증하여 보안과 품질을 보장하는 데 중요한 역할을 합니다.


🌟 Validation의 특징

  1. 표준화된 방식: Java Bean Validation은 JSR 380표준을 따르므로 다양한 프레임워크에서 일관되게 사용 가능합니다.

  2. 어노테이션 기반: @NotNull, @Size 등의 어노테이션을 통해 손쉽게 검증 로직을 추가할 수 있습니다.

  3. 유효성 검사 메시지 제공: 각 검증 어노테이션에 사용자 정의 메시지를 설정해 유연하게 사용할 수 있습니다.

  4. 유연한 커스터마이징: 기본 검증 로직 외에도 Validator 인터페이스를 구현하여 커스텀 Validation을 작성할 수 있습니다.


📋 Validation에서 자주 사용하는 용어

  1. @Valid: DTO(데이터 전달 객체)에서 Validation을 활성화하기 위해 사용됩니다.

  2. @NotNull, @NotEmpty, @NotBlank: 각각 Null, 빈 문자열, 공백만 포함된 문자열을 허용하지 않음을 나타냅니다.

  3. @Size: 문자열이나 컬렉션의 길이를 제한합니다.

  4. @Pattern: 정규식을 사용하여 입력 데이터 형식을 제한합니다.

  5. @Min, @Max: 숫자의 최소값과 최대값을 설정합니다.

  6. @Email: 이메일 형식을 검증합니다.


  • Constraint: 입력값에 부과되는 제약 조건을 정의합니다.

    • 예시: @NotNull, @Size(min=5, max=15)
      @NotNull(message = "이메일은 필수 입력 값입니다.")
      @Email(message = "유효한 이메일 형식이 아닙니다.")
      private String email;
  • Validator: 입력값을 검증하는 도구입니다. 기본 제공되는 표준 Validator 외에도 커스텀 Validator를 구현할 수 있습니다.

    • 예시:
      public class CustomValidator implements ConstraintValidator<CustomConstraint, String> {
         @Override
         public boolean isValid(String value, ConstraintValidatorContext context) {
             return value != null && value.matches("^[a-zA-Z]+$");
         }
      }
  • BindingResult: Controller에서 Validation 오류를 처리하기 위해 사용되는 객체입니다.

    • 예시:
      @PostMapping("/register")
      public ResponseEntity<?> register(@Valid @RequestBody UserDto userDto, BindingResult bindingResult) {
         if (bindingResult.hasErrors()) {
             return ResponseEntity.badRequest().body(bindingResult.getAllErrors());
         }
         return ResponseEntity.ok("회원가입 성공!");
      }

🛠 실무에서 Validation 사용하는 방법

1. 간단한 DTO(Data Transfer Object)에 Validation 적용하기
아래는 회원가입 요청을 처리하는 DTO에서 Validation을 사용하는 예시입니다.

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public class SignupRequest {
    @NotBlank(message = "이름은 필수 항목입니다.")
    private String name;

    @Email(message = "유효한 이메일 주소를 입력하세요.")
    private String email;

    @Size(min = 8, message = "비밀번호는 최소 8자 이상이어야 합니다.")
    private String password;

    // Getters and Setters
}

2. 컨트롤러에서 Validation 활성화
컨트롤러 메서드에서 @Valid 어노테이션을 사용하여 Validation을 활성화할 수 있습니다.

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;

@RestController
@RequestMapping("/api/signup")
public class SignupController {

    @PostMapping
    public String signup(@Valid @RequestBody SignupRequest request) {
        return "회원가입이 완료되었습니다.";
    }
}

3. 검증 실패 시 처리
Spring Boot는 기본적으로 검증 실패 시 MethodArgumentNotValidException을 발생시킵니다. 이를 처리하려면 Exception Handler를 작성할 수 있습니다.

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.MethodArgumentNotValidException;

import java.util.stream.Collectors;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
        String errorMessages = ex.getBindingResult()
                                  .getFieldErrors()
                                  .stream()
                                  .map(error -> error.getField() + ": " + error.getDefaultMessage())
                                  .collect(Collectors.joining(", "));
        return new ResponseEntity<>(errorMessages, HttpStatus.BAD_REQUEST);
    }
}

⚡ Validation 작성 시 주의사항

  1. 필요한 곳에만 Validation 적용: 모든 DTO에 Validation을 적용하면 성능이 저하될 수 있습니다. 필요한 곳에서만 사용하세요.

    • 주의사항: 필수 입력 값인지, 형식이 올바른지 등 요구사항에 따라 적합한 어노테이션을 선택해야 합니다.
    • 예시: 전화번호는 숫자 형식이므로 @Pattern을 사용합니다.
      @Pattern(regexp = "^\d{10,11}$", message = "전화번호는 10~11자리 숫자여야 합니다.")
      private String phoneNumber;
  2. Validation 범위 설정

    • 주의사항: Validation이 필요한 부분과 불필요한 부분을 구분하여 성능 문제를 예방합니다.
    • 예시: 사용자 입력값만 검증하고, 시스템 내부 데이터는 제외합니다.
  3. 유효성 메시지 관리: 유효성 에러 메시지는 사용자 친화적으로 작성해야 합니다. 이를 위해 messages.properties 파일을 사용해 메시지를 관리할 수 있습니다. 또한 Validation 메시지를 하드코딩하지 말고, 메시지 파일로 분리하여 다국어 지원에 대비합니다.

  • 예시:
    • messages.properties 파일:
      email.notnull=이메일은 필수 입력 값입니다.
      email.invalid=유효한 이메일 형식이 아닙니다.
    • DTO:
      @NotNull(message = "{email.notnull}")
      @Email(message = "{email.invalid}")
      private String email;
  1. 커스텀 Validator 사용: 기본 Validation으로 처리하기 어려운 로직은 커스텀 Validator를 만들어 사용하세요.

커스텀 Validator 예시

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPhoneNumber {
    String message() default "유효하지 않은 전화번호 형식입니다.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class PhoneNumberValidator implements ConstraintValidator<ValidPhoneNumber, String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && value.matches("\\d{3}-\\d{3,4}-\\d{4}");
    }
}
  • 커스텀 Validator 작성 시 예외 처리
    커스텀 Validator는 예상치 못한 입력값에 대해 적절한 예외 처리를 해야 합니다.
    • 예시:
      public class NonEmptyListValidator implements ConstraintValidator<NonEmptyList, List<?>> {
        @Override
        public boolean isValid(List<?> value, ConstraintValidatorContext context) {
            if (value == null || value.isEmpty()) {
                return false;
            }
            return true;
        }
      }

🔍 결론: Validation은 데이터의 첫 번째 방어선

Spring Boot Validation은 코드의 복잡성을 줄이고, 데이터를 안전하게 검증하는 데 유용한 도구입니다. 실무에서 활용할 때는 적절한 Validation 적용과 사용자 친화적인 에러 메시지 제공에 중점을 두어야 합니다.


참고문헌

[Spring] Bean Validation

profile
다 먹어버릴거야!

0개의 댓글