-I'm implementing Validation in Spring framework, but I want to filter the result of BindingResult! ・ I want to use a common form for new and update!
View
Simply using Spring tag, userForm is defined in modelAttribute, and input tag and label tag are written. The update side has exactly the same structure.
Form
UserForm.java
@Data
@ConfirmMail(field = "mail", groups = MailGroup3.class)
@ConfirmPassword(field = "password", groups = PasswordGroup4.class)
@CheckBirthday(field = "birthday", groups = ValidGroup2.class)
public class UserForm {
private String id;
@NotBlank(groups = ValidGroup1.class)
@Size(min = 6, max = 20, groups = ValidGroup2.class)
@Pattern(regexp = "^[a-zA-Z0-9]+$", groups = ValidGroup3.class)
private String account;
@NotBlank(groups = PasswordGroup1.class)
@Size(min = 6, max = 12, groups = PasswordGroup2.class)
@Pattern(regexp = "^[a-zA-Z0-9!-/:-@¥[-`{-~]]+$", groups = PasswordGroup3.class)
private String password;
@NotBlank(groups = PasswordGroup1.class)
@Size(min = 6, max = 12, groups = PasswordGroup2.class)
@Pattern(regexp = "^[a-zA-Z0-9!-/:-@¥[-`{-~]]+$", groups = PasswordGroup3.class)
private String confirmPassword;
@NotBlank(groups = ValidGroup1.class)
@Size(min = 1, max = 10, groups = ValidGroup2.class)
private String name;
@NotBlank(groups = ValidGroup1.class)
private String birthday;
@NotBlank(groups = MailGroup1.class)
@Email(groups = MailGroup2.class)
private String mail;
@NotBlank(groups = MailGroup1.class)
@Email(groups = MailGroup2.class)
private String confirmMail;
private String roleName;
}
Blanks, regular expressions, confirmation passwords, confirmation email addresses, and other validations are defined for each item. The fields to be filtered are separate, and it is better to specify GroupOrder. .. ..
Controller
SignupController
@PostMapping("/signup")
String createUser(@ModelAttribute("userForm") @Validated({ GroupOrder.class, PasswordGroupOrder.class, MailGroupOrder.class }) UserForm userForm,
BindingResult result, RedirectAttributes redirectAttributes) {
if(result.hasErrors()) {
return "users/signup/new";
}
mailAuthUserService.authUserbyMail(userForm);
redirectAttributes.addFlashAttribute("resultMessage", "An email has been sent. Please check your mailbox.");
return "redirect:/login";
}
When considering diversion of these to the update side, as a problem (Suppose you bring the information as it is from the DB)
-When updating the password, the password is irreversibly hashed, so the information brought from the DB cannot be entered as it is in the form on the update side (hashing the hashed one at the time of update). Because it will be done). The password must be empty. -When you do not want to update, the confirmation password and confirmation email address items are empty, so validation is applied. It is troublesome to put items one by one.
So validation gets in the way.
When I asked Google teacher, it seems that the method to delete the error item is not defined in BindinResult
, so it can not be done. If you `` redefine the object'' instead of erasing it, you will be able to erase it as a result!
UserController.java
private BindingResult filteringBindingResult(BindingResult result, UserForm userForm) {
UserDto userDto = findById(userForm.getId()); //・ ・ ・ 1
BindingResult tmpResult = new BeanPropertyBindingResult(userForm, "userForm");//・ ・ ・ 2
if(StringUtils.isEmpty(userForm.getPassword()) && userDto.getMail().equals(userForm.getMail())) { //・ ・ ・ 3
for(FieldError fieldError : result.getFieldErrors()) { //・ ・ ・ 4
if(fieldError.getField().equals("confirmMail") ||
fieldError.getField().equals("confirmPassword") ||
fieldError.getField().equals("password")) {
continue;
}
tmpResult.addError(fieldError);
}
return tmpResult;
}
if(StringUtils.isEmpty(userForm.getPassword())) {
for(FieldError fieldError : result.getFieldErrors()) {
if(fieldError.getField().equals("confirmPassword") ||
fieldError.getField().equals("password")) {
continue;
}
tmpResult.addError(fieldError);
}
return tmpResult;
}
if(userDto.getMail().equals(userForm.getMail())) {
for(FieldError fieldError : result.getFieldErrors()) {
if(fieldError.getField().equals("confirmMail")) {
continue;
}
tmpResult.addError(fieldError);
}
return tmpResult;
}
return result;
}
Arguments: BindingResult result (where the error result is stored)
, ```UserForm userForm (item entered on the View side) ``
Return value ・ ・ ・ BindingResult
1 ... Defined to bring user information from the DB and check if the email address has changed.
2 ... Since BindingResult is an interface, it does not have an implementation. BeanPropertyBindingResult is the default implementation of BindingResult. Therefore, it is necessary to assign a filtered error item to this object, so define it.
Springframework
3 ... In this implementation, we want to use valaidation when it needs to be updated, so check if the password is empty and the email address has not changed, and if true, filter to valaidation. When
is multiplied by and
is false, the implementation is such that is multiplied by normal valaidation
.
4 ... With the controller, the received BindingResults are taken out one by one, the ones that match the item to be filtered are skipped with continue, and the others are assigned to tmpResult.
UserController.java
private BindingResult filteringBindingResult(BindingResult result, UserForm userForm) throws Exception {
final BindingResult newResult = new BeanPropertyBindingResult(userForm, "userForm");
Set<FieldError> allErrors = new HashSet<>();
result.getFieldErrors().forEach(x -> allErrors.add(x));
if (StringUtils.isEmpty(userForm.getPassword()) && StringUtils.isEmpty(userForm.getConfirmPassword())) {
allErrors.removeIf(x -> x.getField().equals("password") || x.getField().equals("confirmPassword"));
}
UserDto userDto = userService.findById(userForm.getId());
if (userDto.getMail().equals(userForm.getMail()) && StringUtils.isEmpty(userForm.getConfirmMail())) {
allErrors.removeIf(x -> x.getField().equals("mail") || x.getField().equals("confirmMail"));
}
allErrors.forEach(x -> newResult.addError(x));
if (result.getFieldErrorCount() == newResult.getFieldErrorCount()) {
return result;
} else {
return newResult;
}
}
The BindingResult result is removed only when it is refreshed using a lambda expression and when the confirmation password and password are empty and when the email address has not changed and the confirmation email address is empty
. ..
Controller
UserController.java
@PostMapping("/{id}/edit")
String edit(Model model,
@Validated({ GroupOrder.class, PasswordGroupOrder.class, MailGroupOrder.class }) UserForm userForm,
BindingResult result) {
BindingResult filteringBindingResult = filteringBindingResult(result, userForm); //・ ・ ・ 1
if(filteringBindingResult.hasErrors()) {
model.addAttribute("org.springframework.validation.BindingResult.userForm", filteringBindingResult); //・ ・ ・ 2
return "users/edit";
}
userService.update(userForm);
return "redirect:/user/mypage";
}
1 ... The return value of the method defined earlier is stored in filteringBindingResult
.
2 ... I had a very difficult time here, but at first I was wondering if I could implement it smoothly by rewriting the reference of BindingResult result, but apparently, when this controller was called, it became Model. The result of BindingResult before applying the filter was stored. So you need to overwrite that object. "org.springframework.validation.BindingResult.userForm
print debugs the Model itself and examines it ...
I honestly don't know if it's a best practice. It may be easier to see when you look at it later if you simply separate the Form for update and new registration, but by making the Form common, error messages can also be shared, so I will do this method Was selected. I would be grateful if anyone could be helpful.
If the @GroupSequence
of the validation is all the same, there was a problem that the validation would not get caught when other fields were in error. This is because the filtered BindingResult will not be output after ValidGroup1 due to the specification that ValidGroup2,3,4 below it will not be output if it is caught in ValidGroup1 of GroupOrder.
Therefore, it is necessary to prepare and apply @GroupSequence
separately.
Recommended Posts