5

I have a controller that accepts a dto object. I need to change the fields that are present in the dto object.

@PatchMapping(value = "/update/{uuid}")
    public ResponseEntity<UserDto> update(
            @RequestBody UserDto userDto,
            @PathVariable("uuid")UUID uuid) throws UserNotFoundException {           
        User updatedUser = userService.update(
                userMapper.userDtoToUser(userDto),
                uuid
        );           
        return .....
    }

But a userService can only accept entities. I need to use mapper dto -> entity. But the entity cannot have empty fields that come in dto (Let's say that you need to change only one field). What to do in this situation? I know that the controller should not contain logic

Pikachu
  • 309
  • 4
  • 14
  • Have you considered changing userService to expect for a userDto instead of a userEntity? The service layer should be the one dealing with conversion logics, right? – Sir Beethoven Feb 18 '20 at 19:33
  • Add a constructor in Entity class which accepts a DTO object as only parameter. – narendra-choudhary Feb 18 '20 at 19:37
  • Is your problem the conversion or the validation of the dto fields? – InsertKnowledge Feb 18 '20 at 21:07
  • Patching/prtial update is not easy in Spring MVC. Some discussion here: https://stackoverflow.com/questions/37681555/how-do-i-prevent-hibernate-from-deleting-child-objects-not-present-in-a-json-pos/39424421#39424421 . Possible approach here: https://cassiomolin.com/2019/06/10/using-http-patch-in-spring/ – Alan Hay Feb 19 '20 at 09:35

3 Answers3

2

Two possible ways to resolve this problem. You have to either change the service method to accept the dto and not the entity or you have to create a @Component class which implements Converter, override the convert method and do the necessary field changes there and then @Autowire GenericConversionService in your controller and call genericConversionService.convert(userDto, User.class);

The converter should look like this:

import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

@Component
public class UserDtoToUser implements Converter<UserDto, User> {

    @Override
    public User convert(UserDto source) {
        User user = new User();
        // user.set ..... for all necessary fields
        return user;
    }

}

EDIT

In case you want to check the validity of the fields you're receiving you can simply use the following annotations to ensure your data is correct: @NotBlank - this checks if a field is null or an empty string, @NotNull - this checks if a field is null, @NotEmpty - this checks if a field is null or an empty collection. Important to remember - you must add the @Valid annotation before the object you want to validate (side note - in case of nested objects you also need to add it to your object fields) so it looks like this @RequestBody @Valid UserDto userDto,

And then for the dto it should look something like this:

import java.util.List;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

public class UserDto {
    @NotNull
    private Integer id;

    @NotBlank
    private String username;

    @NotBlank
    private String password;

    @NotEmpty
    private List<String> roles;
}

Change to fields to whatever is in your dto of course. Also in case you need to do more validations there are a number of other validation annotations you can add.

InsertKnowledge
  • 1,012
  • 1
  • 11
  • 17
  • How would this mapping treat nulls in the DTO? How would it know if they were (a) not present in the request or (b) present in the request but explicitly nulled? – Alan Hay Feb 19 '20 at 09:32
  • I initially thought the question was about fields in the User entity which do not _exist_ in the dto but are mandatory but since you're not the first to question this and the OP still hasn't responded to the comment I posted, I will edit this answer to include dto validation as well. – InsertKnowledge Feb 19 '20 at 09:42
0

You could use reflection to check the null properties and BeanUtils for the copying

In Spring that would be the way that I'd to check the empty properties

public static String[] getNullPropertyNames (Object source) {
    final BeanWrapper src = new BeanWrapperImpl(source);
    PropertyDescriptor[] pds = src.getPropertyDescriptors();

    Set<String> emptyNames = new HashSet<>();
    for(PropertyDescriptor pd : pds) {
        Object srcValue = src.getPropertyValue(pd.getName());
        if (srcValue == null) emptyNames.add(pd.getName());
    }
    return emptyNames.toArray(new String[0]);
}

And then for the copying

User updatedUser = new User();
BeanUtils.copyProperties(userDto, updatedUser, getNullPropertyNames(userDto));
EduMelo
  • 455
  • 3
  • 15
0

As you say the controller should not contain logic, so for this Spring has an interface, the interface is Validator

There are pros and cons for considering validation as business logic, and Spring offers a design for validation (and data binding) that does not exclude either one of them. Specifically validation should not be tied to the web tier, should be easy to localize and it should be possible to plug in any validator available. Considering the above, Spring has come up with a Validator interface that is both basic ands eminently usable in every layer of an application.

This is what you need to do:

Spring features a Validator interface that you can use to validate objects. The Validator interface works using an Errors object so that while validating, validators can report validation failures to the Errors object.

we have a DTO to which we will validate the fields:

public class Person {
    private String name;
    private int age;

    // the usual getters and setters...
}

To do validations we must implement Validator interface:

    public class PersonValidator implements Validator {

    /**
     * This Validator validates *just* Person instances
     */
    public boolean supports(Class clazz) {
        return Person.class.equals(clazz);
    }

    public void validate(Object obj, Errors e) {
        if (supports(obj.getClass())) {
            ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
            Person p = (Person) obj;
            if (p.getAge() < 0) {
                e.rejectValue("age", "negativevalue");
            } else if (p.getAge() > 110) {
                e.rejectValue("age", "too.darn.old");
            }
        }
    }
}

If the DTO passes all validations you can map the DTO to an Entity object

You can find more information here Spring Validator

jdurango
  • 533
  • 1
  • 4
  • 15
  • 2
    This is very outdated though. You can now directly annotate the fields themselves with `@NotBlank` and `@Min` and just add `@Valid` in front of the UserDto userDto request body parameter. – InsertKnowledge Feb 18 '20 at 21:02
  • But with this pattern, Pikachu can validate if the field is empty or not which I think is what he is asking and with this way you can manage this situation and have more control – jdurango Feb 18 '20 at 21:09