틀린 해석이 있다면 알려주세요
https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#chapter-groups
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
5. Grouping constraints
validator 및 ExecutableValidator 의 모든 유효성 검사 메서드는 이 grouping 을 사용한다
이 parameter 에 대해 살펴본다.
5.1. Requesting groups
group 을 사용하면 유효성 검사 중에 적용되는 제약 조건을 제한시킬 수 있다.
각단계에서 지정된 제약 조건만 검사를 할 수 있도록 한다.
해당 그룹에만 절절한 var - arg 매개 변수로 전달되어 유효성 검사를 진행시킬 수 있다.
쉽게 말해 지정된 애노테이션을 붙여야만 그룹에 저장되어 검사가 된다.
* 둘 이상의 그룹 요청 시 그룹의 유효성 검사 순서가 절대적인 순서로 진행되지는 않는다고 한다.
만약 그룹 지정이 없으면 기본 그룹으로 검사된다. javax.validation.groups.Default
예시를 들어볼 클래스
package org.hibernate.validator.referenceguide.chapter05;
public class Person {
@NotNull
private String name;
public Person(String name) {
this.name = name;
}
// getters and setters ...
}
Person class 를 Driver 클래스에서 확장시켜주는 예제이다
애노테이션에 보이는 groups 에 내용들은 다 grouping 시켜준 코드이다.
이렇게 하면, person 클래스를 상속하는 driver 클래스를 그룹화한 것이다.
package org.hibernate.validator.referenceguide.chapter05;
public class Driver extends Person {
@Min(
value = 18,
message = "You have to be 18 to drive a car",
groups = DriverChecks.class
)
public int age;
@AssertTrue(
message = "You first have to pass the driving test",
groups = DriverChecks.class
)
public boolean hasDrivingLicense;
public Driver(String name) {
super( name );
}
public void passedDrivingTest(boolean b) {
hasDrivingLicense = b;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
인터페이스화 한 안전한 그룹화 (쉽게 리팩토링이 가능)
package org.hibernate.validator.referenceguide.chapter05;
public interface DriverChecks {
}
세번째 예시는 일부는 default 제약 조건을 사용하고, 일부는 groups 한 예시를 보여준다
package org.hibernate.validator.referenceguide.chapter05;
public class Car {
@NotNull
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
@Min(2)
private int seatCount;
@AssertTrue(
message = "The car has to pass the vehicle inspection first",
groups = CarChecks.class
)
private boolean passedVehicleInspection;
@Valid
private Driver driver;
public Car(String manufacturer, String licencePlate, int seatCount) {
this.manufacturer = manufacturer;
this.licensePlate = licencePlate;
this.seatCount = seatCount;
}
public boolean isPassedVehicleInspection() {
return passedVehicleInspection;
}
public void setPassedVehicleInspection(boolean passedVehicleInspection) {
this.passedVehicleInspection = passedVehicleInspection;
}
public Driver getDriver() {
return driver;
}
public void setDriver(Driver driver) {
this.driver = driver;
}
// getters and setters ...
}
package org.hibernate.validator.referenceguide.chapter05;
public interface CarChecks {
}
세가지 예제를 살펴보자면
- The constraints on Person.name, Car.manufacturer, Car.licensePlate and Car.seatCount all belong to the Default group (기본 그룹)
- The constraints on Driver.age and Driver.hasDrivingLicense belong to DriverChecks
- The constraint on Car.passedVehicleInspection belongs to the group CarChecks
이렇게 각각의 그룹을 검증할때 Validator 로 검증 결과가 어떻게 나오는지 확인해본다
// create a car and check that everything is ok with it.
Car car = new Car( "Morris", "DD-AB-123", 2 );
Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );
assertEquals( 0, constraintViolations.size() );
// but has it passed the vehicle inspection?
constraintViolations = validator.validate( car, CarChecks.class );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"The car has to pass the vehicle inspection first",
constraintViolations.iterator().next().getMessage()
);
// let's go to the vehicle inspection
car.setPassedVehicleInspection( true );
assertEquals( 0, validator.validate( car, CarChecks.class ).size() );
// now let's add a driver. He is 18, but has not passed the driving test yet
Driver john = new Driver( "John Doe" );
john.setAge( 18 );
car.setDriver( john );
constraintViolations = validator.validate( car, DriverChecks.class );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"You first have to pass the driving test",
constraintViolations.iterator().next().getMessage()
);
// ok, John passes the test
john.passedDrivingTest( true );
assertEquals( 0, validator.validate( car, DriverChecks.class ).size() );
// just checking that everything is in order now
assertEquals(
0, validator.validate(
car,
Default.class,
CarChecks.class,
DriverChecks.class
).size()
);
There are no validation errors, even though the property passedVehicleInspection is per default false as the constraint defined on this property does not belong to the default group.
The next validation using the CarChecks group fails until the car passes the vehicle inspection. Adding a driver to the car and validating against DriverChecks again yields one constraint violation due to the fact that the driver has not yet passed the driving test. Only after setting passedDrivingTest to true the validation against DriverChecks passes.
The last validate() call finally shows that all constraints are passing by validating against all defined groups.
5.2. Group inheritance
위의 방법은 각 검증 그룹을 모두 validate() 하거나 각각 지정해주어야한다
이때 다른 그룹에 포함하는 제약 조건 그룹까지 모두 정의할 수 있다 -> 그룹상속
package org.hibernate.validator.referenceguide.chapter05.groupinheritance;
public class SuperCar extends Car {
@AssertTrue(
message = "Race car must have a safety belt",
groups = RaceCarChecks.class
)
private boolean safetyBelt;
// getters and setters ...
}
보면 기본 Default 그룹을 상속하고 있다
그룹자체를 또 그룹화하고 있어서 검사 시에 한번에 확인이 가능하다
package org.hibernate.validator.referenceguide.chapter05.groupinheritance;
import javax.validation.groups.Default;
public interface RaceCarChecks extends Default {
}
In the example below, we will check if a SuperCar with one seat and no security belts is a valid car and if it is a valid race-car.
// create a supercar and check that it's valid as a generic Car
SuperCar superCar = new SuperCar( "Morris", "DD-AB-123", 1 );
assertEquals( "must be greater than or equal to 2", validator.validate( superCar ).iterator().next().getMessage() );
// check that this supercar is valid as generic car and also as race car
Set<ConstraintViolation<SuperCar>> constraintViolations = validator.validate( superCar, RaceCarChecks.class );
assertThat( constraintViolations ).extracting( "message" ).containsOnly(
"Race car must have a safety belt",
"must be greater than or equal to 2"
);
On the first call to validate(), we do not specify a group. There is one validation error because a car must have at least one seat. It is the constraint from the Default group.
On the second call, we specify only the group RaceCarChecks. There are two validation errors: one about the missing seat from the Default group, another one about the fact that there is no safety belts coming from the RaceCarChecks group.
5.3. Defining group sequences
이번엔 두개 이상의 시퀀스를 정의하는 방법을 확인해본다
기본적인 제약 조건 + 자동차의 제약 조건 + 실제 운전자의 조건
이 세가지를 모두 확인한다
package org.hibernate.validator.referenceguide.chapter05;
import javax.validation.GroupSequence;
import javax.validation.groups.Default;
@GroupSequence({ Default.class, CarChecks.class, DriverChecks.class })
public interface OrderedChecks {
}
대신!!!!! 계단식 정의 또는 직간접적인 순환 종속성은 관여할 수 없다
에러남 GroupDefinitionException
Using a group sequence
Car car = new Car( "Morris", "DD-AB-123", 2 );
car.setPassedVehicleInspection( true );
Driver john = new Driver( "John Doe" );
john.setAge( 18 );
john.passedDrivingTest( true );
car.setDriver( john );
assertEquals( 0, validator.validate( car, OrderedChecks.class ).size() );
깰끔해짐
5.4. Redefining the default group sequence
5.4.1. @GroupSequence
아예 @GroupSequence 애노테이션을 사용해서 재정의할 수도 있다
package org.hibernate.validator.referenceguide.chapter05;
@GroupSequence({ RentalChecks.class, CarChecks.class, RentalCar.class })
public class RentalCar extends Car {
@AssertFalse(message = "The car is currently rented out", groups = RentalChecks.class)
private boolean rented;
public RentalCar(String manufacturer, String licencePlate, int seatCount) {
super( manufacturer, licencePlate, seatCount );
}
public boolean isRented() {
return rented;
}
public void setRented(boolean rented) {
this.rented = rented;
}
}
package org.hibernate.validator.referenceguide.chapter05;
public interface RentalChecks {
}
RentalCar rentalCar = new RentalCar( "Morris", "DD-AB-123", 2 );
rentalCar.setPassedVehicleInspection( true );
rentalCar.setRented( true );
Set<ConstraintViolation<RentalCar>> constraintViolations = validator.validate( rentalCar );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"Wrong message",
"The car is currently rented out",
constraintViolations.iterator().next().getMessage()
);
rentalCar.setRented( false );
constraintViolations = validator.validate( rentalCar );
assertEquals( 0, constraintViolations.size() );
Since there must be no cyclic dependency in the group and group sequence definitions, one cannot just add Default to the sequence redefining Default for a class. Instead the class itself has to be added.
재정의 하는 것은 Default 에 정의된 클래스 로컬이라 전파되거나 하지는 않는다
그룹을 default 에 전파시킬 뿐이다
여기서 변환 규칙을 선언하여 그룹을 제어할 수도 있다
5.4.2. @GroupSequenceProvider
해당 애노테이션을 사용하면 객체 상태에 따라 시퀀스를 동적 재정의 하기 위한 SPI 를 제공한다
이를 사용하려면
인터페이스 구현 -> DefaultGroupSequenceProvider 대상 클래스 등록
순서를 따라야한다.
package org.hibernate.validator.referenceguide.chapter05.groupsequenceprovider;
public class RentalCarGroupSequenceProvider
implements DefaultGroupSequenceProvider<RentalCar> {
@Override
public List<Class<?>> getValidationGroups(RentalCar car) {
List<Class<?>> defaultGroupSequence = new ArrayList<Class<?>>();
defaultGroupSequence.add( RentalCar.class );
if ( car != null && !car.isRented() ) {
defaultGroupSequence.add( CarChecks.class );
}
return defaultGroupSequence;
}
}
package org.hibernate.validator.referenceguide.chapter05.groupsequenceprovider;
@GroupSequenceProvider(RentalCarGroupSequenceProvider.class)
public class RentalCar extends Car {
@AssertFalse(message = "The car is currently rented out", groups = RentalChecks.class)
private boolean rented;
public RentalCar(String manufacturer, String licencePlate, int seatCount) {
super( manufacturer, licencePlate, seatCount );
}
public boolean isRented() {
return rented;
}
public void setRented(boolean rented) {
this.rented = rented;
}
}
5.5. Group conversion
확인 시에 운전자 클래스 확인 후, 자동차 클래스 확인까지 함께 하고 싶다면 어떻게 해야할까?
이번엔 @ConvertGroup 을 사용한다
일단 각 클래스에서
그룹화를 지정해주고
먼저 확인할 Driver 클래스를 지정해준 뒤
package org.hibernate.validator.referenceguide.chapter05.groupconversion;
public class Driver {
@NotNull
private String name;
@Min(
value = 18,
message = "You have to be 18 to drive a car",
groups = DriverChecks.class
)
public int age;
@AssertTrue(
message = "You first have to pass the driving test",
groups = DriverChecks.class
)
public boolean hasDrivingLicense;
public Driver(String name) {
this.name = name;
}
public void passedDrivingTest(boolean b) {
hasDrivingLicense = b;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// getters and setters ...
}
이를 함께 체크하는 Car 클래스를 작업한다
Driver 클래스 를 보면 드라이버 체크까지 같이할 수 있게 한다
그럼 운전자 클래스 체크 후 -> 자동차 체크를 다 하는 Car class 가 만들어진다.
package org.hibernate.validator.referenceguide.chapter05.groupconversion;
@GroupSequence({ CarChecks.class, Car.class })
public class Car {
@NotNull
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
@Min(2)
private int seatCount;
@AssertTrue(
message = "The car has to pass the vehicle inspection first",
groups = CarChecks.class
)
private boolean passedVehicleInspection;
@Valid
@ConvertGroup(from = Default.class, to = DriverChecks.class)
private Driver driver;
public Car(String manufacturer, String licencePlate, int seatCount) {
this.manufacturer = manufacturer;
this.licensePlate = licencePlate;
this.seatCount = seatCount;
}
public boolean isPassedVehicleInspection() {
return passedVehicleInspection;
}
public void setPassedVehicleInspection(boolean passedVehicleInspection) {
this.passedVehicleInspection = passedVehicleInspection;
}
public Driver getDriver() {
return driver;
}
public void setDriver(Driver driver) {
this.driver = driver;
}
// getters and setters ...
}
테스트 코드도 간단해짐
// create a car and validate. The Driver is still null and does not get validated
Car car = new Car( "VW", "USD-123", 4 );
car.setPassedVehicleInspection( true );
Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );
assertEquals( 0, constraintViolations.size() );
// create a driver who has not passed the driving test
Driver john = new Driver( "John Doe" );
john.setAge( 18 );
// now let's add a driver to the car
car.setDriver( john );
constraintViolations = validator.validate( car );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"The driver constraint should also be validated as part of the default group",
constraintViolations.iterator().next().getMessage(),
"You first have to pass the driving test"
);
You can define group conversions wherever @Valid can be used, namely associations as well as method and constructor parameters and return values. Multiple conversions can be specified using @ConvertGroup.List.
그치만 제한사항이 있다
- @ConvertGroup must only be used in combination with @Valid. If used without, a ConstraintDeclarationException is thrown.
- It is not legal to have multiple conversion rules on the same element with the same from value. In this case, a ConstraintDeclarationException is raised.
- The from attribute must not refer to a group sequence. A ConstraintDeclarationException is raised in this situation.
@ConvertGroup 과 @Valid 를 같이 써야함.
from 값이 동일한 요소의 규칙을 가지면 안됨 (자동차 체크하는 곳에 자동차체크를 하면 안됨)
from attribute 값이 동일할 때 여러 변환 규칙을 가지면 안됨
아니면 셋 다 ConstraintDeclarationException 에러 발생
Rules are not executed recursively. The first matching conversion rule is used and subsequent rules are ignored. For example if a set of @ConvertGroup declarations chains group A to B and B to C, the group A will be converted to B and not to C.
'Web > DB' 카테고리의 다른 글
[Hibernate orm] 11. Fetching (0) | 2023.02.16 |
---|---|
[Spring framework Data JPA] 5.1.5. Specifications (0) | 2023.02.09 |
[Hibernate validator] 6. Creating custom constraints (1) | 2023.02.02 |
[Hibernate orm] 2.7. Associations (1) | 2023.02.02 |
[Spring JPA] Update Query 작성 시 @Modifying @Transactional (0) | 2023.01.11 |