SpringBoot

유효성 검사

똑똑한망치 2024. 2. 28. 23:39
728x90
반응형

1. 유효성 검사


애플리케이션의 비즈니스 로직이 올바르게 동작하려면 데이터를 사전 검증하는 작업이 필요하다. 이것을 유효성 검사 또는 데이터 검증이라고 한다.

 

(1) 일반적인 애플리케이션 유효성 검사의 문제점

  • 계층별로 진행되는 유효성 검사는 검사 로직이 각 클래스별로 분산돼 있어 관리하기가 어렵다.
  • 검증 로직에 의외로 중복이 많아 여러 곳에 유사한 기능의 코드가 존재할 수 있다.
  • 검증해야 할 값이 많다면 검증하는 코드가 길어진다. 

이러한 문제들로 코드가 복잡해지고 가독성이 떨어진다.

이를 해결하기 위해 자바 진영에서 Bean Validation이라는 데이터 유효성 검사 프레임워크를 제공한다.

Bean Vaildation은 어노테이션을 통해 다양한 데이터를 검증하는 기능을 제공한다.

즉, 유효성 검사를 위한 로직을 DTO 같은 도메인 모델과 묶어서 각 계층에서 사용하면서 검증 자체를 도메인 모델이 얹는 방식으로 수행한다.

 

 

(2) Hibernate Vaildator

Hibernate Validator는 Bean Validation 명세의 구현체이다. 스프링 부트에서는 Hibernate Validator를 유효성 검사 표준으로 채택해서 사용하고 있다.

 

 

(3) 스프링 부트의 유효성 검사

유효성 검사는 각 계층으로 데이터가 넘어오는 시점에 해당 데이터에 대한 검사를 실시한다.

스프링 부트 프로젝트에서는 계층 간 데이터 전송에 DTO 객체를 활용하고 있기 때문에 유효성 검사를 DTO 객체를 기준으로 수행하는 것이 일반적이라고 볼 수 있다.

 

 

문자열 검증

  • @Null : null 값만 허용
  • @NotNull : null을 허용하지 않는다. "" , " " 는 허용한다.
  • @NotEmpty : ""을 허용하지 않는다. " " 는 허용한다.
  • @NotBlank : null, "", " " 을 모두 허용하지 않는다.

최댓값 / 최솟값 검증

  • BigDecimal, BigInteger, int, long 등의 타입을 지원한다.
  • @DemicalMax( value = "$numberString") : $numberString 보다 작은 값을 허용한다.
  • @Demicalmin( value = "$numberString") : $numberString 보다 큰 값을 허용한다.
  • @Min( value = $number) : $number 이상의 값을 허용한다.
  • @Max( value = $number) : $number 이하의 값을 허용한다.

값의 범위 검증

  • BigDecimal, BigInteger, int, long 등의 타입을 지원한다.
  • @Positive : 양수를 허용한다.
  • @PositiveOrZero : 0을 포함한 양수 허용
  • @Negative : 음수 허용
  • @NegativeOrZero : 0을 포함한 음수 허용

시간에 대한 검증

  • Date, LocalDate, LocalDateTime 등의 타입 지원
  • @Future : 현재보다 미래의 날짜 허용
  • @FutureOrPresent : 현재를 포함한 미래의 날짜 허용
  • @Past : 현재보다 과거의 날짜 허용
  • @PastOrPresent : 현재를 포함한 과거의 날짜 허용

이메일 검증

  • @Email : 이메일 형식을 검사, "" 는 허용

자릿수 범위 검증

  • BigDecimal, BigInteger, int, long 등의 타입을 지원한다.
  • @Digits(integer = $number1, fraction = $number2) : $number1의 정수 자릿수와 $number2의 소수 자릿수를 허용

Boolean 검증

  • @AssertTrue : true 인지 체크, null 값은 체크하지 않는다.
  • @AssertFalse : false 인지 체크, null 값은 체크하지 않는다.

문자열 길이 검증

  • Size(min = $number1, max = $number2) : $number1 이상 $number2 이하의 범위 허용

정규식 검증

  • @Patern(regexp = "$expression") : 정규식 검사, 정규식은 java의 java.util.regex.Pattern 패키지의 컨벤션을 따른다.

 

@PostMapping("/valid")
pubilc ResponseEntity<String> checkValidationByValid( 
	@Valid @ReqeustBody ValidRequestDto validRequestDto) {
    LOGGER.info(validRequestDto.toString());
    return ResponseEntity.status(HttpStatus.OK).body(validRequestDto.toString());
}

 

위의 예시처럼 @Valid 어노테이션을 지정해야 DTO 객체에 대한 유효성 검사를 수행한다.

 

 

 

 

(4) @Validated 활용

위의 예제에서는 유효성 검사를 위해 @Valid 어노테이션을 선언했다. 

@Valid 어노테이션은 자바에서 지원하는 어노테이션이며, 스프링도 @Validated 라는 별도의 어노테이션으로 유효성 검사를 지원한다.

@Validated 은 @Valid 어노테이션의 기능을 포함하고 있기 때문에 @Validated 로 변경할 수 있다. 또한 @Validated 는 유효성 검사를 그룹으로 묶어 대상을 특정할 수 있는 기능이 있다.

@Validated 어노테이션에 특정 그룹을 지정하지 않는 경우 groups 속성을 설정하지 않은 필드에 대해서만 유효성 검사를 실시한다.

 

// DTO 객체에 그룹 설정

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class ValidatedRequestDto {
	@NotBlank
    private String name;
    
    @Pattern(regexp = "01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$")
    private String phoneNumber;
    
    @Min(value = 20, groups = ValidationGroup1.class)
    @Max(value = 40, groups = ValidationGroup1.class)
    private int age;
    
    ...
    
}
// 그룹1
@PostMapping("/validated/group1")
public ResponseEntity<String> checkValidation1(
	@Validated(ValidationGroup1.class) @RequestBody ValidatedRequestDto validatedRequestDto) {
    	LOGGER.info(calidatedRequestDto.toString());
        return ResponseEntity.status(HttpStatus.OK).body(validatedRequestDto.toString());
}

// 그룹2
@PostMapping("/validated/group2")
public ResponseEntity<String> checkValidation2(
	@Validated(ValidationGroup2.class) @RequestBody ValidatedRequestDto validatedRequestDto) {
    	LOGGER.info(calidatedRequestDto.toString());
        return ResponseEntity.status(HttpStatus.OK).body(validatedRequestDto.toString());
}

 

모두 정리하자면

  • @Validated 어노테이션에 특정 그룹을 설정하지 않은 경우에는 groups가 설정되지 않은 필드에 대해 유효성 검사를 수행
  • @Validated 어노테이션에 특정 그룹을 설정하는 경우에는 지정된 그룹으로 설정된 필드에 대해서만 유효성 검사를 수행

 

 

(5) 커스텀 Validation 추가

ConstraintValidator와 커스텀 어노테이션을 조합해 별도의 유효성 검사 어노테이션을 생성할 수 있다.

동일한 정규식을 계속 쓰는 @Pattern 어노테이션의 경우가 가장 흔한 사례이다.

 

먼저, ConstraintValidator 인터페이스를 구현하는 클래스를 생성해야 한다.

public class TelephoneValidator implements ConstraintValidator<Telephone, String> {
	
    @Override
    public boolean isValid(Stinrg value, ConstraintValidatorContext context) {
    	if(value == null) {
        	return false;
        }
        return value.matches("01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$");
    }
}

직접 유효성 검사 로직을 작성해야 한다.

 

 

두번째로, 어노테이션 인터페이스를 생성한다.

@Target(ElemenType.FIELD)
@Retention(RetentonPolicy.RUNTIME)
@Constraint(validatedBy = TelephoneValidator.class)
public @interface Telephone {
	String message() default "전화번호 형식이 일치하지 않습니다.";
    Class[] groups() default {};
    Class[] payload() default {};
}

 

  • @Target : 이 어노테이션을 어디서 선언할 수 있는지 정의하는데 사용
  • @Retention : 이 어노테이션이 실제로 적용되고 유지된느 범위를 의미
    • RetentionPolicy.RUNTIME : 컴파일 이후에도 JVM에 의해 계속 참조
    • RetentionPolicy.CLASS : 컴파일러가 클래스를 참조할 대 까지 유지
    • RetentionPolicy.SOURCE : 컴파일 전까지만 유지, 컴파일 이후에는 사라짐
  • @Constraint : 앞에서 생성한 인터페이스와 매핑하는 작업 수행

인터페이스 내부에는 message(), groups(), payload() 요소를 정의해야 한다.

  • message() : 유효성 검사가 실패할 경우 반환되는 메시지
  • groups() : 유효성 검사를 사용하는 그룹으로 설정
  • payload() : 사용자가 추가 정보를 위해 전달하는 값
반응형

'SpringBoot' 카테고리의 다른 글

액추에이터  (0) 2024.03.10
예외 처리  (0) 2024.02.28
연관관계 매핑  (0) 2024.02.22
JPA Auditing  (0) 2024.02.14
Spring Data JPA 활용  (0) 2024.02.13