본문 바로가기

카테고리 없음

Bean Validation 사용하기

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>

종속성을 낮추기위해 사용한다고 들음
이런식으로 사용가능 -> 나머지는 위 사이트 doc 참고하면 될듯

 

 

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{

}