틀린 해석이 있다면 알려주세요
6.1. Creating a simple constraint
많은 방식의 표준 제약 조건이 정의되어있지만,
커스텀해서 사용해야할 경우가 있다.
문서를 보면서 간단한 valid constraint 를 만들어보려고 한다
참고로 간단한 제약조건이라고 되어있는데
엄청 길다
ㅋㅋㅋㅋㅋㅋㅋ
To create a custom constraint, the following three steps are required:
- Create a constraint annotation
- Implement a validator
- Define a default error message
예제에서 사용할 클래스는
여기서 licensePlate 에 적용해볼 것이라고 한다
package org.hibernate.validator.referenceguide.chapter01;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class Car {
@NotNull
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
@Min(2)
private int seatCount;
public Car(String manufacturer, String licencePlate, int seatCount) {
this.manufacturer = manufacturer;
this.licensePlate = licencePlate;
this.seatCount = seatCount;
}
//getters and setters ...
}
6.1.1. The constraint annotation
붙여줄 annotation 을 표현할 enum 을 만들어준다
public enum CaseMode {
UPPER,
LOWER;
}
Defining the @CheckCase constraint annotation
무서워보일 수 있지만 어렵지않다고 설명해준다..ㅎㅎ..
If you’ve never designed an annotation before, this may look a bit scary, but actually it’s not that hard:
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
@Repeatable(List.class)
public @interface CheckCase {
String message() default "{org.hibernate.validator.referenceguide.chapter06.CheckCase." +
"message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
CaseMode value();
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
@interface List {
CheckCase[] value();
}
}
애노테이션 유형은 @interface 를 이용하여 정의
message - 제약 조건 위반 시에 오류메세지 생성 기본 키를 반환할 수 있는 속성을 지정해준 것이다
groups - 그룹화제약조건을 허용하는 속성으로 Class<?> [] 유형의 빈 배열이다
payload - 이게 사용자가 지정 제약 조건을 사용할 수 있게 해준다. API 자체에서 사용하는게 아닌 사용자의 severity 로 정의해줄 수 있다. 어디로 payload 시켜줄지도 사용자가 지정할 수 있는 것.
serverity
public class Severity {
public interface Info extends Payload {
}
public interface Error extends Payload {
}
}
public class ContactDetails {
@NotNull(message = "Name is mandatory", payload = Severity.Error.class)
private String name;
@NotNull(message = "Phone number not specified, but not mandatory",
payload = Severity.Info.class)
private String phoneNumber;
// ...
}
Now a client can after the validation of a ContactDetails instance access the severity of a constraint using ConstraintViolation.getConstraintDescriptor().getPayload() and adjust its behavior depending on the severity.
클라이언트가 유효성 검사를 하면 severity 에 액세스하여 해당 결과에 따라 동작을 나눌 수 있다.
이 세가지는 필수 속성이고, 이외에도 다른 케이스를 지정할 수 있도록 할 수 있는데 이게 바로 상단에 있는 애노테이션들이다.
얘네도 해석을 해보자면
@Target 은 지원해줄 유형이다 필드에서만 사용할건지, 메소드에도 사용해줄건지 등등 허용해줄 범위를 지정해준다 자세한건 문서를 더 보면서 공부해야할 듯 하다. 복잡하니 밑에 6.4, 2.1.4, 6.3 링크 달아둠
@Retention(RUNTIME) 는 리플렉션을 통해 런타임에 사용할 수 있도록 지정 -> 애노테이션이 실행시에 사용할 수 있도록 함
@Constraint(validatedBy = CheckCaseValidator.class) : 유효성 검사할 클래스를 지정, 여러 데이터 유형에 사용할 경우 각 데이터 유형에 하나씩 여러 유효성 검사기가 지정될 수 있다
@Documented : @CheckCase 라는 주석이 달린 요소의 JavaDoc에 포함된다고 한다
@Repeatable(List.class) : 구성이 다른 동일한 위치에서 주석이 여러번 반복할 수 있음을 알림
6.4 Constraint composition
Hibernate Validator 6.2.5.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
2.1.4. Class-level constraints
Hibernate Validator 6.2.5.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
6.3. Cross-parameter constraints
Hibernate Validator 6.2.5.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
6.1.2. The constraint validator
유효성을 검사할 validator 를 만들어야한다.
package org.hibernate.validator.referenceguide.chapter06;
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
private CaseMode caseMode;
@Override
public void initialize(CheckCase constraintAnnotation) {
this.caseMode = constraintAnnotation.value();
}
@Override
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
if ( object == null ) {
return true;
}
if ( caseMode == CaseMode.UPPER ) {
return object.equals( object.toUpperCase() );
}
else {
return object.equals( object.toLowerCase() );
}
}
}
그 밖의 오류가 날 경우 (!isValid) 일 때 오류 메세지도 추가할 수 있다
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorcontext;
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
private CaseMode caseMode;
@Override
public void initialize(CheckCase constraintAnnotation) {
this.caseMode = constraintAnnotation.value();
}
@Override
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
if ( object == null ) {
return true;
}
boolean isValid;
if ( caseMode == CaseMode.UPPER ) {
isValid = object.equals( object.toUpperCase() );
}
else {
isValid = object.equals( object.toLowerCase() );
}
if ( !isValid ) {
constraintContext.disableDefaultConstraintViolation();
constraintContext.buildConstraintViolationWithTemplate(
"{org.hibernate.validator.referenceguide.chapter06." +
"constraintvalidatorcontext.CheckCase.message}"
)
.addConstraintViolation();
}
return isValid;
}
}
It is important to add each configured constraint violation by calling addConstraintViolation(). Only after that the new constraint violation will be created.
보통 기본적인 Expression Language 에는 사용자 위반이 활성화되지않으나
만약 그런 요구사항이 있다면 아래의 HibernateConstraintValidatorContext 를 사용해야한다
12.13.1. HibernateConstraintValidatorContext
Hibernate Validator 6.2.5.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
6.1.2.2. The HibernateConstraintValidator extension
initalize() 메서드에서 더 많은 정보를 제공한다
package org.hibernate.validator.referenceguide.chapter06;
public class MyFutureValidator implements HibernateConstraintValidator<MyFuture, Instant> {
private Clock clock;
private boolean orPresent;
@Override
public void initialize(ConstraintDescriptor<MyFuture> constraintDescriptor,
HibernateConstraintValidatorInitializationContext initializationContext) {
this.orPresent = constraintDescriptor.getAnnotation().orPresent();
this.clock = initializationContext.getClockProvider().getClock();
}
@Override
public boolean isValid(Instant instant, ConstraintValidatorContext constraintContext) {
//...
return false;
}
}
You should only implement one of the initialize() methods. Be aware that both are called when initializing the validator.
initalize는 메소드 중에 하나만 구현해야한다. 아니면 초기화 시 둘다 호출 됨
HibernateConstraintValidator 는 두개의 변수를 사용하고 있다
ConstraintDescriptor 제약조건
HibernateConstraintValidatorInitializationContext 시계 공급자 또는 임시 유효성 검사 허용 오차와 같은 유용한 도우미 및 컨텍스트 정보를 제공
6.1.2.3. Passing a payload to the constraint validator
일부 external parameters 에 대하여 제약 조건 유효성 검사의 동작을 조건화할 수 있다
상황에 따라 하나의 인스턴스에 LOCALE 이 다르면 조건이 다를 수 있다
이로 인해 validaor 와 ValidatorFactory 를 사용하여 제약 조건 유효검사가 가능하다
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.constraintValidatorPayload( "US" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
HibernateValidatorFactory hibernateValidatorFactory = Validation.byDefaultProvider()
.configure()
.buildValidatorFactory()
.unwrap( HibernateValidatorFactory.class );
Validator validator = hibernateValidatorFactory.usingContext()
.constraintValidatorPayload( "US" )
.getValidator();
// [...] US specific validation checks
validator = hibernateValidatorFactory.usingContext()
.constraintValidatorPayload( "FR" )
.getValidator();
// [...] France specific validation checks
이렇게 조건 별 유효성 검사기를 추가해주고
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorpayload;
public class ZipCodeValidator implements ConstraintValidator<ZipCode, String> {
public String countryCode;
@Override
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
if ( object == null ) {
return true;
}
boolean isValid = false;
String countryCode = constraintContext
.unwrap( HibernateConstraintValidatorContext.class )
.getConstraintValidatorPayload( String.class );
if ( "US".equals( countryCode ) ) {
// checks specific to the United States
}
else if ( "FR".equals( countryCode ) ) {
// checks specific to France
}
else {
// ...
}
return isValid;
}
}
isValid 안에서 분기별 조건 검사를 할 수 있다
HibernateConstraintValidatorContext#getConstraintValidatorPayload() has a type parameter and returns the payload only if the payload is of the given type.
t is important to note that the constraint validator payload is different from the dynamic payload you can include in the constraint violation raised.
The whole purpose of this constraint validator payload is to be used to condition the behavior of your constraint validators. It is not included in the constraint violations, unless a specific ConstraintValidator implementation passes on the payload to emitted constraint violations by using the constraint violation dynamic payload mechanism.
이는 동적페이로드와 다를 수 있다.
6.1.3. The error message
The last missing building block is an error message which should be used in case a @CheckCase constraint is violated. To define this, create a file ValidationMessages.properties with the following contents (see also Section 4.1, “Default message interpolation”):
Hibernate Validator 6.2.5.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
org.hibernate.validator.referenceguide.chapter06.CheckCase.message=Case mode must be {value}.
package org.hibernate.validator.referenceguide.chapter04;
public class Car {
@NotNull(message = "The manufacturer name must not be null")
private String manufacturer;
//constructor, getters and setters ...
}
이런 식으로 오류 message 설정이 가능하다. 커스텀해도 사용할 수 있다는 뜻
이제 드디어 사용해보기...ㅎㅎ...
사용법은 매우 간단하다
6.1.4. Using the constraint
package org.hibernate.validator.referenceguide.chapter06;
public class Car {
@NotNull
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
@CheckCase(CaseMode.UPPER)
private String licensePlate;
@Min(2)
private int seatCount;
public Car(String manufacturer, String licencePlate, int seatCount) {
this.manufacturer = manufacturer;
this.licensePlate = licencePlate;
this.seatCount = seatCount;
}
//getters and setters ...
}
@CheckCase 애노테이션만 붙이면 된다
위반할 경우에는
//invalid license plate
Car car = new Car( "Morris", "dd-ab-123", 4 );
Set<ConstraintViolation<Car>> constraintViolations =
validator.validate( car );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"Case mode must be UPPER.",
constraintViolations.iterator().next().getMessage()
);
//valid license plate
car = new Car( "Morris", "DD-AB-123", 4 );
constraintViolations = validator.validate( car );
assertEquals( 0, constraintViolations.size() );
이렇게 메세지가 나올 것이다
이렇게 하면 간단한(?) 제약조건이 끝난다
6.2. Class-level constraints
개체 상태 확인을 위해 클래스 수준에서도 제약조건을 적용할 수 있다
필드만이 아니라, 클래스 자체의 값들을 가져올 수 있다
위에 있던 예제인데 보면 클래스에도 이렇게 사용하고
package org.hibernate.validator.referenceguide.chapter06.classlevel;
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { ValidPassengerCountValidator.class })
@Documented
public @interface ValidPassengerCount {
String message() default "{org.hibernate.validator.referenceguide.chapter06.classlevel." +
"ValidPassengerCount.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
package org.hibernate.validator.referenceguide.chapter06.classlevel;
public class ValidPassengerCountValidator
implements ConstraintValidator<ValidPassengerCount, Car> {
@Override
public void initialize(ValidPassengerCount constraintAnnotation) {
}
@Override
public boolean isValid(Car car, ConstraintValidatorContext context) {
if ( car == null ) {
return true;
}
return car.getPassengers().size() <= car.getSeatCount();
}
}
위에 간단한 방식은 스트링 object 를 가져왔지만
클래스도 가져와서 확인해볼 수 있다는 뜻이다
Car class 의 값들로 체크가 가능하다
you need to use the element type TYPE in the @Target annotation. This allows the constraint to be put on type definitions. The validator of the constraint in the example receives a Car in the isValid() method and can access the complete object state to decide whether the given instance is valid or not.
@Target 을 이용하여 어디까지 허용해줄건지 지정해주면 된다
6.2.1. Custom property paths
For instance you might want to report the @ValidPassengerCount constraint against the passengers property instead of the Car bean.
이렇게 ValidPassengetCount 라는 클래스도 같이 가져와서 Car 과 연관하여 유효성을 검사할 수 있다
package org.hibernate.validator.referenceguide.chapter06.custompath;
public class ValidPassengerCountValidator
implements ConstraintValidator<ValidPassengerCount, Car> {
@Override
public void initialize(ValidPassengerCount constraintAnnotation) {
}
@Override
public boolean isValid(Car car, ConstraintValidatorContext constraintValidatorContext) {
if ( car == null ) {
return true;
}
boolean isValid = car.getPassengers().size() <= car.getSeatCount();
if ( !isValid ) {
constraintValidatorContext.disableDefaultConstraintViolation();
constraintValidatorContext
.buildConstraintViolationWithTemplate( "{my.custom.template}" )
.addPropertyNode( "passengers" ).addConstraintViolation();
}
return isValid;
}
}
6.3. Cross-parameter constraints
Generic constraints (which have been discussed so far) apply to the annotated element, e.g. a type, field, container element, method parameter or return value etc. Cross-parameter constraints, in contrast, apply to the array of parameters of a method or constructor and can be used to express validation logic which depends on several parameter values.
일반 제약(지금까지 논의된)은 유형, 필드, 컨테이너 요소, 메서드 매개변수 또는 반환 값 등과 같은 주석이 달린 요소에 적용됩니다.
반대로 교차 매개변수 제약은 메서드 또는 매개변수의 배열에 적용됩니다.
생성자이며 여러 매개 변수 값에 따라 달라지는 유효성 검사 논리를 표현하는 데 사용할 수 있습니다.
교차 매개변수 제약은 변수 값이 달라지는 경우에도 유효성을 검사할 수 있도록 처리할 수 있다.
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
이 주석을 달고 사용하면 된다.
제약조건 정의는 일반제약과 마찬가지로 message(), groups(), payload() @constraint,
target에는 METHOD and CONSTRUCTOR also ANNOTATION_TYPE is specified as target of the annotation,
in order to enable the creation of composed constraints based on
@Constraint(validatedBy = ConsistentDateParametersValidator.class)
@Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface ConsistentDateParameters {
String message() default "{org.hibernate.validator.referenceguide.chapter04." +
"crossparameter.ConsistentDateParameters.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
As discussed above, the validation target PARAMETERS must be configured for a cross-parameter validator by using the @SupportedValidationTarget annotation.
그리고 이렇게 @SupportedValiationTarget << 을 붙여준다
어떤 변수인지 정해지지 않더라도 안에서 유효성 검사를 할 수 있다
일반과 마찬가지로 null 파라미터는 유효하게 간주해줄 것, 싫으면 @NotNULL
package org.hibernate.validator.referenceguide.chapter06.crossparameter;
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ConsistentDateParametersValidator implements
ConstraintValidator<ConsistentDateParameters, Object[]> {
@Override
public void initialize(ConsistentDateParameters constraintAnnotation) {
}
@Override
public boolean isValid(Object[] value, ConstraintValidatorContext context) {
if ( value.length != 2 ) {
throw new IllegalArgumentException( "Illegal method signature" );
}
//leave null-checking to @NotNull on individual parameters
if ( value[0] == null || value[1] == null ) {
return true;
}
if ( !( value[0] instanceof Date ) || !( value[1] instanceof Date ) ) {
throw new IllegalArgumentException(
"Illegal method signature, expected two " +
"parameters of type Date."
);
}
return ( (Date) value[0] ).before( (Date) value[1] );
}
}
Just obtain a node builder from the ConstraintValidatorContext passed to isValid() and add a parameter node by calling addParameterNode().
또, 제약조건이 일반 및 교차 매개변수가 모두 있을 수가 있다.
제약 조건에 @SupportedValidationTarget({ValidationTarget.PARAMETERS, ValidationTarget.ANNOTATED_ELEMENT})
이런식으로 되어있는 경우도 해당한다
이럴때는 제약조건 지정을 해야한다
보면 constraint 에 두 종류의 validaor 가 달려있고,
package org.hibernate.validator.referenceguide.chapter06.crossparameter;
@Constraint(validatedBy = {
ScriptAssertObjectValidator.class,
ScriptAssertParametersValidator.class
})
@Target({ TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface ScriptAssert {
String message() default "{org.hibernate.validator.referenceguide.chapter04." +
"crossparameter.ScriptAssert.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
String script();
ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
}
validationAppliesTo() 를 IMPLICIT 로 지정해두면 자동으로 파생시킬 수 있다
예제를 보면 조금 더 쉽게 이해할 수 있다
@ScriptAssert(script = "arg1.size() <= arg0", validationAppliesTo = ConstraintTarget.PARAMETERS)
public Car buildCar(int seatCount, List<Passenger> passengers) {
//...
return null;
}
제약조건 + 제약조건 을 둘다 체크하는 제약조건 => ScriptAssert
6.4. Constraint composition
제약조건을 주려면 기본 세개의 제약조건이 들어가야하는데 여러가지의 제약 조건이 붙는다면 이를 통합할 수 있다
package org.hibernate.validator.referenceguide.chapter06.constraintcomposition;
@NotNull
@Size(min = 2, max = 14)
@CheckCase(CaseMode.UPPER)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = { })
@Documented
public @interface ValidLicensePlate {
String message() default "{org.hibernate.validator.referenceguide.chapter06." +
"constraintcomposition.ValidLicensePlate.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
이 ValidLicensePlate valid 조건 안에는
@NotNull
@Size
@CheckCase
조건이 다 들어있다
If the composed constraint itself requires a validator, this validator is to be specified within the @Constraint annotation. For composed constraints which don’t need an additional validator such as @ValidLicensePlate, just set validatedBy() to an empty array.
@ValidLicensePlate와 같은 추가 유효성 검사기가 필요하지 않은 구성된 제약 조건의 경우 validationdBy()를 빈 배열로 설정한다
간단하게 유효성 검사가 가능하다
package org.hibernate.validator.referenceguide.chapter06.constraintcomposition;
public class Car {
@ValidLicensePlate
private String licensePlate;
//...
}
@ReportAsSingleViolation
-> 다른 애노테이션들을 상속해서 기능을 만들 때 여러개의 애노테이션을 상속 받을때 메세지가 여러개가 생기기 때문에
하나의 메세지만 띄우기 위해서 사용한다
package org.hibernate.validator.referenceguide.chapter06.constraintcomposition.reportassingle;
//...
@ReportAsSingleViolation
public @interface ValidLicensePlate {
String message() default "{org.hibernate.validator.referenceguide.chapter06." +
"constraintcomposition.reportassingle.ValidLicensePlate.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
'Web > DB' 카테고리의 다른 글
[Spring framework Data JPA] 5.1.5. Specifications (0) | 2023.02.09 |
---|---|
[Hibernate validator] 5. Grouping constraints (0) | 2023.02.06 |
[Hibernate orm] 2.7. Associations (1) | 2023.02.02 |
[Spring JPA] Update Query 작성 시 @Modifying @Transactional (0) | 2023.01.11 |
[MAC MySql] db 세팅 부터 Spring 연동까지 (0) | 2023.01.04 |