Spring REST partial update with @PATCH method

Viewed 41151

I'm trying to implement a partial update of the Manager entity based in the following:

Entity

public class Manager {
    private int id;
    private String firstname;
    private String lastname;
    private String username;
    private String password;

    // getters and setters omitted
}

SaveManager method in Controller

@RequestMapping(value = "/save", method = RequestMethod.PATCH)
public @ResponseBody void saveManager(@RequestBody Manager manager){
    managerService.saveManager(manager);
}

Save object manager in Dao impl.

@Override
public void saveManager(Manager manager) {  
    sessionFactory.getCurrentSession().saveOrUpdate(manager);
}

When I save the object the username and password has changed correctly but the others values are empty.

So what I need to do is update the username and password and keep all the remaining data.

5 Answers

ObjectMapper.updateValue provides all you need to partially map your entity with values from dto. As an addition, you can use either of two here: Map<String, Object> fields or String json, so your service method may look like this:

@Autowired
private ObjectMapper objectMapper;

@Override
@Transactional
public Foo save(long id, Map<String, Object> fields) throws JsonMappingException {
    Foo foo = fooRepository.findById(id)
    .orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));

    return objectMapper.updateValue(foo , fields);
}

As a second solution and addition to Lane Maxwell's answer you could use Reflection to map only properties that exist in a Map of values that was sent, so your service method may look like this:

@Override
@Transactional
public Foo save(long id, Map<String, Object> fields) {

    Foo foo = fooRepository.findById(id)
    .orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));

    fields.keySet()
            .forEach(k -> {
                Method method = ReflectionUtils.findMethod(LocationProduct.class, "set" + StringUtils.capitalize(k));
                if (method != null) {
                    ReflectionUtils.invokeMethod(method, foo, fields.get(k));
                }
            });
    return foo;
}

Second solution allows you to insert some additional business logic into mapping process, might be conversions or calculations ect.

Also unlike finding reflection field Field field = ReflectionUtils.findField(Foo.class, k); by name and than making it accessible, finding property's setter actually calls setter method that might contain additional logic to be executed and prevents from setting value to private properties.

Related