⚡ 개요
개발을 하면서 데이터 스키마를 기반으로 직렬화 가능한 DTO를 설계 하고 프론트엔드와 백엔드간의 데이터를 주고 받는 작업을 하게 된다.
이때 클라이언트의 정보가 잘못되었을 가능성도 있고, 위조되어 넘어올 가능성도 존재한다. 이를 방지하기 위해서 백엔드에서도 한번더 검증 하는 로직을 작성 하는게 좋다.
⚡ Validation Annotation을 사용하는가?
- 유효성 검증은 신뢰할 수 있는 시스템을 위한 첫 관문
- 사용자 입력은 언제나 신뢰할 수 없다.
- 서버는 항상 방어적으로 동작해야 한다.
- Validation Annotation은 선언형으로 검증 로직을 간단하게 처리
- 코드 중복 없이, 컨벤션에 따라 쉽게 적용 가능
- Spring, Jakarta EE, Hibernate Validator와 같은 프레임워크와의 통합
⚡ 기본 개념과 구조
Bean Validation 이란?
- Java에서 제공하는 표준 유효성 검증 프레임워크 (JSR 380: Bean Validation 2.0)
- 구현체: Hibernate Validator가 대표적 이다. (Hibernate JPA와는 관련이 없다.)
동작 방식
- DTO에 어노테이션 기반 제약 조건을 선언
- @Valid 혹은 @Validated를 통해 검증 실행
- 검증 실패 시 예외 발생
⚡ 사용 방법
아래의 코드를 보게 되면 최대한 많이 사용할거라고 판단되는 어노테이션 내용에 대해서 작업 해보았다.
package com.service.tddsample.dto;
import jakarta.validation.constraints.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDate;
import java.util.List;
@Getter
@Setter
public class TestInfo {
@NotBlank(message = "필수 입력값입니다.")
@Pattern(regexp = "^[a-zA-Z0-9]{5,10}$", message = "영문 및 숫자로 5~10자 이내여야 합니다.")
private String adminId;
@NotBlank(message = "필수 입력값 입니다.")
@Size(min = 3, max = 50, message = "최소 3자 이상, 최대 50자 이하여야 합니다.")
private String name;
@NotBlank(message = "이메일은 필수 입력값입니다.")
@Email(message = "올바른 이메일 형식이어야 합니다.")
private String adminEmail;
@Min(value = 1, message = "최소 1이어야 합니다.")
@Max(value = 2, message = "최대 2까지만 가능합니다.")
private Long count;
@DecimalMin(value = "0.5", message = "최소 0.5 이상이어야 합니다.")
@DecimalMax(value = "10.0", message = "최대 10.0 이하여야 합니다.")
private Double energyRate;
@Past(message = "시작일은 과거 날짜여야 합니다.")
private LocalDate startDate;
@Future(message = "종료일은 미래 날짜여야 합니다.")
private LocalDate endDate;
@Size(min = 1, max = 5, message = "최소 1개에서 최대 5개까지만 입력 가능합니다.")
private List<String> testSubList;
}
🚀 커스텀 Validator 만드는 법
두개의 날짜 사이를 검증 하는 Validator 를 만든다고 가정 했을때의 어노테이션이다.
@Documented
// TODO 실제 예외처리 로직을 구현한 구현체
@Constraint(validatedBy = DateBetweenValidator.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DateBetween {
// TODO 기본 메세지
String message() default "{isNotBetween}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
실제 구현체는 @Constraint 어노테이션에 선언한 클래스내에서 구현 해주면 된다.
세부코드 내용은 구현하지 않았습니다. :)
public class DateBetweenValidator implements ConstraintValidator<DateBetween, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
// TODO 코드 구현
return false;
}
}
추가적으로 커스텀 어노테이션을 만들때 @Target 정보 이다.
METHOD | 메서드에 적용 | @Transactional |
FIELD | 필드(변수)에 적용 | @NotNull |
ANNOTATION_TYPE | 다른 어노테이션을 만들 때 사용 | @Documented |
CONSTRUCTOR | 생성자에 적용 | @Inject |
PARAMETER | 메서드 매개변수에 적용 | @Valid |
TYPE_USE | 타입(제네릭, 배열, 변수 선언) 전체에 적용 | @NotNull T |
📌 결론
Validation은 버그보다 싸다
- 입력 단계에서 문제를 막을 수 있다면, 시스템 전체의 복잡도와 버그는 줄어든다.
- Annotation 기반 유효성 검사는 선언적이며 테스트 하기 쉽다.
- 도입 난이도 대비 효과가 큰 기능이므로 반드시 적용하는게 좋아 보인다.
반응형
'Spring' 카테고리의 다른 글
[JPA] Entity Default 값 저장 이슈 정리 (0) | 2024.08.21 |
---|---|
[QueryDSL] SQLQueryFactory 사용시 LocalDate 타입 반환시 날짜 에러 (0) | 2024.06.01 |
Spring boot + vue 프로젝트 (2) (1) | 2024.04.06 |
Spring boot + vue 프로젝트 (1) (0) | 2024.04.04 |
[JPA] Spring boot 3.x.x 에서 QueryDsl Date Type 사용 방법 (0) | 2024.01.09 |