The Bean Validation reference implementation. - Hibernate Validator
The Bean Validation reference implementation. - Hibernate Validator
Express validation rules in a standardized way using annotation-based constraints and benefit from transparent integration with a wide variety of frameworks.
hibernate.org
https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/
Hibernate Validator 6.2.3.Final - Jakarta Bean Validation Reference Implementation: Reference Guide
Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone. To avoid duplication of th
docs.jboss.org
애플리케이션을 개발할 때, 데이터의 유효성을 검사하는 것은 일반적으로 애플리케이션 전체에서 발생합니다. TOAST Cloud의 메시징 플랫폼 상품인 Notification은 메시지, 이메일 주소 형식, 수신/발신자의 번호 등 클라이언트의 입력값에 대해 많은 검증을 진행합니다. 그리고 입력값 검증 실패에 대해 원인을 쉽게 파악하고 이해하기 쉽게 적절한 API 응답을 해야합니다. 이런 목표를 달성하기 위해 Java의 데이터 유효성 검사 표준 기술인 Bean Validation을 선택했습니다.
해결 방법
Java에서는 2009년부터 Bean Validation이라는 데이터 유효성 검사 프레임워크를 제공하고 있다. Bean Validation은 위에서 말한 문제들을 해결하기 위해 다양한 제약(Contraint)을 도메인 모델(Domain Model)에 어노테이션(Annotation)로 정의할 수 있게한다. 이 제약을 유효성 검사가 필요한 객체에 직접 정의하는 방법으로 기존 유효성 검사 로직의 문제를 해결한다.
https://meetup.toast.com/posts/223
Validation 어디까지 해봤니? : NHN Cloud Meetup
TOAST Cloud의 메시징 플랫폼 상품인 Notification은 메시지, 이메일 주소 형식, 수신/발신자의 번호 등 클라이언트의 입력값에 대해 많은 검증을 진행합니다.
meetup.toast.com
사용하기
pom.xml에 dependency 추가해준다.
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.3.Final</version>
</dependency>
Bean Validation
스프링의 기본적인 validation인 Bean validation은 클래스 "필드"에 특정 annotation을 적용하여 필드가 갖는 제약 조건을 정의하는 구조로 이루어진 검사입니다.
validator가 어떠한 비즈니스적 로직에 대한 검증이 아닌, 그 클래스로 생성된 객체 자체의 필드에 대한 유효성 여부를 검증합니다.
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member {
@NotBlank
private String id;
@NotNull
@Pattern(regexp = "(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,20}"
, message = "패스워드는 대문자, 소문자, 특수문자가 적어도 하나씩은 있어야 하며 최소 8자리여야 하며 최대 20자리까지 가능합니다.")
private String password;
@NotNull
@Range(min = 1900, max = 2021)
private Integer year;
}
위에서 사용한 검증 애노테이션은 아래와 같습니다.
- @NotNull: null을 허용하지 않음
- @NotBlank: 빈칸 혹은 공백만 있는 경우를 허용 안 함
- @Pattern: 정규표현식 (Regex) 적용
- @Range: 범위 안의 값이어야 함 (따라서, 1900 ~ 2021의 범위 내만 허용)
validateUtils
package kr.or.ddit.validate;
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
import kr.or.ddit.vo.MemberVO;
public class ValidateUtils {
static Validator validator;
static {
validator = Validation.byDefaultProvider()
.configure()
.messageInterpolator(
new ResourceBundleMessageInterpolator(
new PlatformResourceBundleLocator( "kr.or.ddit.msgs.errorMessage" )
)
)
.buildValidatorFactory()
.getValidator();
}
public static <T> boolean validate(T target, Map<String, String> errors, Class...groups) {
Set<ConstraintViolation<T>> violations = validator.validate(target, groups);
boolean valid = violations==null || violations.size()==0;
for( ConstraintViolation<T> singleViolation : violations) {
String property = singleViolation.getPropertyPath().toString();
String message = singleViolation.getMessage();
errors.put(property, message);
}
return valid;
}
}
package kr.or.ddit.validate.constraints;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
/**
* 어노테이션 : 사람과 시스템에 동시에 정보를 전달하기 위한 주석의 한 형태.
* 커스텀 어노테이션의 필수 정책
* 1. @Taget : 어노테이션의 사용 위치 설정.
* 2. @Retention : 어노테이션의 사용(생존) 범위 설정.(SOURCE, CLASS, RUNTIME)
* 어노테이션의 3가지 사용형태
* 1. Marker annotation ex) @TelNumber - 필수 속성이 없는 어노테이션
* 2. Single value annotation ex) @TelNumber("text") - value 라는 이름을 가진 속성에 한해 사용되는 형태
* 3. Multi value annotation ex) @TelNumber(value="text", message="text") value 이외의 모든 속성을 이름이 반드시 필요함.
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=TelNumberValidator.class)
public @interface TelNumber {
String value() default "\\d{2,3}-\\d{3,4}-\\d{4}";
String message() default "{kr.or.ddit.validate.contraints.TelNumber.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
package kr.or.ddit.validate.constraints;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 문자열 타입의 프로퍼티 하나의 값에 대한 검증.
*
*/
public class TelNumberValidator implements ConstraintValidator<TelNumber, String>{
private TelNumber annotation;
@Override
public void initialize(TelNumber constraintAnnotation) {
this.annotation = constraintAnnotation;
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
boolean valid = value==null || value.isEmpty();
if(!valid){
// 전화번호 입력(value) 형식(정규식)에 대한 검증.
String regexp = annotation.value();
valid = value.matches(regexp);
}
return valid;
}
}
package kr.or.ddit.validate;
import javax.validation.groups.Default;
public interface InsertGroup extends Default{
}