Hello, I'm messing with Spring for the first time in a while. I haven't used it for about half a year at work.
As the title suggests, I made a note of the following contents.
I want to check the consistency with the master data stored in the database.
Implement a conference room reservation form. In the conference room reservation form, select the conference room to use with the radio button, and enter the date and time of use and the number of people. There are multiple meeting rooms, each with a fixed capacity. If more than the capacity is entered, an input error will occur.
Get the conference room information based on the entered conference room ID. Obtain the capacity from the information in the conference room and determine whether the entered number of users is less than or equal to the capacity. If the value is invalid, a validation message will be displayed for the capacity form control.
We named it SeatingCapacityValid
because it is a validation of the capacity.
According to the validation specifications, at least the conference room ID and the capacity field name must be received as annotation arguments.
Each must be declared as a method.
SeatingCapacityValid.java
@Documented
@Constraint(validatedBy = {SeatingCapacityValidator.class}) //Specify the class that implements the validation logic.
@Target(ElementType.TYPE) //Because it's validation for multiple attributes of the form,Designated to be granted to the class.
@Retention(RetentionPolicy.RUNTIME)
public @interface SeatingCapacityValid{
//Specify the key of the message to be displayed by default.
String message() default "{com.example.demo.form.validator.SeatingCapacityValid.message}";
String roomIdProperty(); //Receive specified the name of the property where the entered conference room ID is stored.
String numberOfUsersProperty(); //Receive specified the name of the property where the entered number of users is stored.
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
SeatingCapacityValid[] value();
}
}
Implements the ConstraintValidator
interface.
The first type argument is an annotation, and the target to which the annotation is added (in this case, the Form class, but for versatility, the Object type) is specified.
SeatingCapacityValidator.java
public class SeatingCapacityValidator implements ConstraintValidator<SeatingCapacityValid, Object> {
@Autowired
private RoomRepository roomRepository;
private String roomIdProperty;
private String timesProperty;
private String message;
@Override
public void initialize(CommodityTimesValid constraintAnnotation) {
roomIdProperty = constraintAnnotation.roomIdProperty();
numberOfUsersProperty = constraintAnnotation.numberOfUsersProperty();
message = constraintAnnotation.message();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BeanWrapper beanWrapper = new BeanWrapperImpl(value);
Integer roomId = (Integer)beanWrapper.getPropertyValue(roomIdProperty);
Integer numberOfUsers = (Integer) beanWrapper.getPropertyValue(numberOfUsersProperty);
if (roomId == null || numberOfUsers == null) {
return true;
}
Optional<Room> mayBeRoom = roomRepository.findOne(roomId);
return mayBeRoom.map(room -> {
Integer capacity = room.getCapacity();
if (capacity >= numberOfUsers) {
return true;
//Customize TODO messages
context
.buildConstraintViolationWithTemplate(message)
.addPropertyNode(numberOfUsersProperty)
.addConstraintViolation();
return false;
}).orElse(true);
}
}
The message display logic is described in the part written as TODO in SeatingCapacityValidator.java
.
In the property file, we define `{0} as the number of people less than {1} and the goal is to display the capacity of each room in {1}.
SeatingCapacityValidator.java
public class SeatingCapacityValidator implements ConstraintValidator<SeatingCapacityValid, Object> {
//Omission
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
//Omission
return mayBeRoom.map(room -> {
Integer capacity = room.getCapacity();
if (capacity >= numberOfUsers) {
return true;
}
HibernateConstraintValidatorContext hibernateContext =
context.unwrap(HibernateConstraintValidatorContext.class);
hibernateContext.disableDefaultConstraintViolation();
hibernateContext
.addMessageParameter("1", capacity) //Here is the number of people in each room{1}Set to.
.buildConstraintViolationWithTemplate(message)
.addPropertyNode(numberOfUsersProperty)
.addConstraintViolation();
return false;
}).orElse(true);
}
}
In validation using annotation, you should be careful about the following.
@Target
.Where can annotations be added? Is it a single field validation or a correlation check? Is it really necessary to annotate?
This time, I wanted to bind the input value from the screen to an object that puts it in the domain layer. So I created an annotation so that it will be evaluated at the time of binding.
@Constraint
.Which class should the validation logic be delegated to?
Make sure that the necessary information for validation is received from the annotation.
The message template placeholders ({0}
or {1}
) are replaced with the information that can be obtained from the annotation.
{0}
is the field name, and after {1}
is the argument of the annotation.
To set something other than the above, use the ConstraintValidatorContext # unwrap (HibernateConstraintValidatorContext.class)
method to convert to the HibernateConstraintValidatorContext class, and then set the string to be displayed.
The name of the method to use | Placeholder notation |
---|---|
addMessageParameter(String s, Object o) | {s} |
addExpressionVariable(String s, Object o) | ${s} |
There are many parts that I haven't written about the prerequisites, so I think I'll organize it when the whole code is released.