[JAVA] Filter the result of BindingResult [Spring]

Target audience

-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!

Confirmation of the object

View

コメント 2020-05-16 103732.png

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.

コメント 2020-05-16 111346.png

Countermeasures

Is it possible to delete some error items of BindinResult?

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!

Filter method

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.

2020/6/10 update

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 ...

Impressions

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.

2020/06/09 Edit

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

Filter the result of BindingResult [Spring]
Filter the fluctuations of raw data
Spring validation was important in the order of Form and BindingResult
The story of encountering Spring custom annotation
Get the result of POST in Java
About the initial display of Spring Framework
[Java] [Spring] Test the behavior of the logger
About the official start guide of Spring Framework
The story of raising Spring Boot 1.5 series to 2.1 series
Let's check the feel of Spring Boot + Swagger 2.0
Get Body part of HttpResponse with Filter of Spring
How to display the result of form input
The official name of Spring MVC is Spring Web MVC
[Spring] Pitfalls of BeanUtils.copyProperties
[For beginners] DI ~ The basics of DI and DI in Spring ~
Do not easily round the result of decimal calculation
Access the built-in h2db of spring boot with jdbcTemplate
05. I tried to stub the source of Spring Boot
I tried to reduce the capacity of Spring Boot
Judgment of the calendar
The world of clara-rules (4)
The world of clara-rules (1)
About DI of Spring ②
The world of clara-rules (3)
Until the use of Spring Data and JPA Part 2
The world of clara-rules (5)
Until the use of Spring Data and JPA Part 1
The idea of quicksort
A review note of the Spring Framework Resource interface
Check the result of generic parameter inference with JShell
Check the behavior of include, exclude, ExhaustedRetryException of Spring Retry
Control the processing flow of Spring Batch with JavaConfig.
A record of studying the Spring Framework from scratch
Overview of Spring AOP
The idea of jQuery
The story of raising Spring Boot from 1.5 series to 2.1 series part2
Put the file in the properties of string in spring xml configuration
A story packed with the basics of Spring Boot (solved)
Site where you can see the version relation of spring (?)
Let's grasp the operation image (atmosphere) of the DI container of Spring
About the handling of Null
Docker monitoring-explaining the basics of basics-
Execution result memo of String.substring
About the description of Docker-compose.yml
Understand the basics of docker
The play of instantiating java.lang.Void
Explanation of the FizzBuzz problem
The basics of Swift's TableView
Median of the three values
The illusion of object orientation
Try the Spring WebFlux tutorial
Switch the version of bundler
I want to understand the flow of Spring processing request parameters
Get a proxy instance of the component itself in Spring Boot
Part 4: Customize the behavior of OAuth 2.0 Login supported by Spring Security 5
Get Flux result of Spring Web Flux from JS with Fetch API
See the behavior of entity update with Spring Boot + Spring Data JPA
I want to control the default error message of Spring Boot