📜 ⬆️ ⬇️

Configuring DTO Validation in the Spring Framework

Hello! Today we will deal with the validation of data entering through a Data Transfer Object (DTO), and we will configure annotations and visibility so that we receive and give only what we need.

So, we have the DTO class UserDto, with the appropriate fields:

public class UserDto { private Long id; private String name; private String login; private String password; private String email; } 

I omit constructors and getters-setters - I am sure you know how to create them, but I don’t see any reason to increase the code 3-4 times - let’s imagine that they already exist.
')
We will accept DTO through a controller with CRUD methods. Again, I will not write all the CRUD methods - for the purity of the experiment, we have enough pair. Let it be create and updateName.

  @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<UserDto> create(@RequestBody UserDto dto) { return new ResponseEntity<>(service.save(dto), HttpStatus.OK); } @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<UserDto> updateName(@RequestBody UserDto dto) { return new ResponseEntity<>(service.update(dto), HttpStatus.OK); } 

For clarity, they also had to be simplified. Thus, we get some JSON, which is converted to UserDto, and return UserDto, which is converted to JSON and sent to the client.

Now I suggest to get acquainted with those several annotations of validation with which we will work.

  @Null //   null @NotNull //    null @Email //   e-mail 

All annotations can be found in the javax.validation.constraints library. So, we will configure our DTO in such a way as to immediately receive a validated object for further translation into the essence and save to the database. Those fields that must be filled, we will mark NotNull , also mark the e-mail:

 public class UserDto { @Null //   private Long id; @NotNull private String name; @NotNull private String login; @NotNull private String password; @NotNull @Email private String email; } 

We set the validation settings for the DTO - all fields must be filled in except for id - it is generated in the database. Add validation to the controller:

  @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<UserDto> create(@Validated @RequestBody UserDto dto) { return new ResponseEntity<>(service.save(dto), HttpStatus.OK); } @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<UserDto> updateName(@Validated @RequestBody UserDto dto) { return new ResponseEntity<>(service.update(dto), HttpStatus.OK); } 

A validation configured in this way is suitable for creating a new user, but it is not suitable for updating existing ones - after all, we will need to get an id (which is set as null), and also skip the login, password and email fields, because in updateName we change only the name . That is, we need to get the id and name, and nothing more. And here we need visibility interfaces.

Let's create an interface directly in the DTO class (for clarity, I recommend placing such things in a separate class, or better, in a separate package, for example, transfer). The interface will be called New, the second will be called Exist, from which we will inherit UpdateName (in the future we will be able to inherit other visibility interfaces from Exist, we will change more than one name):

 public class User { interface New { } interface Exist { } interface UpdateName extends Exist { } @Null //   private Long id; @NotNull private String name; @NotNull private String login; @NotNull private String password; @NotNull @Email private String email; } 

Now we mark our annotations with the New interface.

  @Null(groups = {New.class}) private Long id; @NotNull(groups = {New.class}) private String name; @NotNull(groups = {New.class}) private String login; @NotNull(groups = {New.class}) private String password; @NotNull(groups = {New.class}) @Email(groups = {New.class}) private String email; 

Now these annotations only work when specifying the New interface. All we have to do is set the annotations for the case when we need to update the name field (let me remind you, we need to specify non-null id and name, the rest are null). Here's what it looks like:

  @Null(groups = {New.class}) @NotNull(groups = {UpdateName.class}) private Long id; @NotNull(groups = {New.class, UpdateName.class}) private String name; @NotNull(groups = {New.class}) @Null(groups = {UpdateName.class}) private String login; @NotNull(groups = {New.class}) @Null(groups = {UpdateName.class}) private String password; @NotNull(groups = {New.class}) @Null(groups = {UpdateName.class}) @Email(groups = {New.class}) private String email; 

Now we need to set the necessary settings in the controllers, set the interface to set the validation:

  @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<UserDto> create(@Validated(UserDto.New.class) @RequestBody UserDto dto) { return new ResponseEntity<>(service.save(dto), HttpStatus.OK); } @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<UserDto> updateName(@Validated(UserDto.UpdateName.class) @RequestBody UserDto dto) { return new ResponseEntity<>(service.update(dto), HttpStatus.OK); } 

Now for each method will be called its own set of settings.

So, we figured out how to validate the input data, now it remains to validate the output. This is done using the @JsonView annotation.

Now in the output DTO, which we give back, all fields are contained. But suppose we never need to give the password (except in exceptional cases).

To validate the output DTO, we will add two more interfaces that will be responsible for the visibility of the output data - Details (for display to users) and AdminDetails (for display only for admins). Interfaces can be inherited from each other, but for simplicity of perception now we will not do this - just an example with input data in this regard.

  interface New { } interface Exist { } interface UpdateName extends Exist { } interface Details { } interface AdminDetails { } 

Now we can annotate the fields as we need (everything is visible except the password):

  @Null(groups = {New.class}) @NotNull(groups = {UpdateName.class}) @JsonView({Details.class}) private Long id; @NotNull(groups = {New.class, UpdateName.class}) @JsonView({Details.class}) private String name; @NotNull(groups = {New.class}) @Null(groups = {UpdateName.class}) @JsonView({Details.class}) private String login; @NotNull(groups = {New.class}) @Null(groups = {UpdateName.class}) @JsonView({AdminDetails.class}) private String password; @NotNull(groups = {New.class}) @Null(groups = {UpdateName.class}) @Email(groups = {New.class}) @JsonView({Details.class}) private String email; 

It remains to mark the desired controller methods:

  @JsonView(Details.class) @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<UserDto> create(@Validated(UserDto.New.class) @RequestBody UserDto dto) { return new ResponseEntity<>(service.save(dto), HttpStatus.OK); } @JsonView(Details.class) @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<UserDto> updateName(@Validated(UserDto.UpdateName.class) @RequestBody UserDto dto) { return new ResponseEntity<>(service.update(dto), HttpStatus.OK); } 

And some other time we will annotate the @JsonView (AdminDetails.class) method, which will only pull the password. If we want the admin to receive all the information, and not just the password, annotate all the required fields accordingly:

  @Null(groups = {New.class}) @NotNull(groups = {UpdateName.class}) @JsonView({Details.class, AdminDetails.class}) private Long id; @NotNull(groups = {New.class, UpdateName.class}) @JsonView({Details.class, AdminDetails.class}) private String name; @NotNull(groups = {New.class}) @Null(groups = {UpdateName.class}) @JsonView({Details.class, AdminDetails.class}) private String login; @NotNull(groups = {New.class}) @Null(groups = {UpdateName.class}) @JsonView({AdminDetails.class}) private String password; @NotNull(groups = {New.class}) @Null(groups = {UpdateName.class}) @Email(groups = {New.class}) @JsonView({Details.class, AdminDetails.class}) private String email; 

I hope this article has helped deal with the validation of input DTOs and the visibility of the output data.

Source: https://habr.com/ru/post/343960/


All Articles