6

I am using Spring data jpa for persistence. Say, I have to update a model. This model has 'n' number of fields along with a primary Key.

    {
    "some_model":{
        "id":"5527716",
        "field_one": "44248156",
        "field_two": "44248156",
        "field_three": "44248156",
        "field_four": "44248156",
        "field_five": "44248156",
        "field_six": "44248156",
        "field_seven": "44248156",
        "field_eight": "44248156",
        "field_nine":"65037768"     
    }
}

Considering above json as a representation of my model, I want to update only those fields which are incoming in the json (primary key is Id which will always be there). Is there any way I can achieve without having to check each field explicitly and setting them?

Say I get a request like:

{
    "some_model":{
        "id":"5527716",
        "field_one": "44248156",
        "field_two": "44248156",
        "field_three": "44248156",

        "field_eight": "44248156",
        "field_nine":"65037768"     
    }
}

I want to achieve update (upsert kind of thing) just using repository.save or something similar. Currently using repository's save method internally updates the model using primaryKey value but sets the values for absent fields as null. I want those fields to remain unchanged.

Main problem is: there are hundreds of models and each model has around 10-15 fields and explicitly checking each field and updating for all the models would be a nightmare.

I also checked about @DynamicUpdate annotation but it seems to be applicable for explicit setting where generated sql is optimized.

I would appreciate any ideas regarding this.

Sudip Bhandari
  • 2,165
  • 1
  • 27
  • 26

4 Answers4

4

Usually merge will work for you since you got detached copy - the one you got from JSON and a persisted one (coming from the repository). Merge does exactly that - updates the persisted property with the values from the detached one and if there isn't one it creates it (I am not sure if that part will be desireable). Still making it automatic you lose the flexibility you have in writing it yourself.

With JPA the merge method is part of the EntityManager. So you will need to inject it and call it like em.merge(detachedEntity);

Other than that there are libraries to do that but I haven't used any. And the best solution that requires some coding is to write a method to manually update the fields ;)

Veselin Davidov
  • 7,031
  • 1
  • 15
  • 23
0

Take a look at dozer - a bean mapping library. You can configure dozer with a custom field mapper to not copy a field if it is null.

You can retrieve the object from the DB, copy the field values from the request object to the object retrieved from the DB (with the custom field mapper that doesn't map null values) and then save the object.

    User user1 = new User(); // Request body
    user1.setFirstName("newFn");

    User user2 = new User(); // Entity retrieved from the database
    user2.setFirstName("fn");
    user2.setLastName("ln");

    DozerBeanMapper dozerBeanMapper = new DozerBeanMapper();
    dozerBeanMapper.setCustomFieldMapper(
            (source, destination, sourceFieldValue, classMap, fieldMapping) -> sourceFieldValue == null);
    dozerBeanMapper.map(user1, user2);
    System.out.println(user2); // User(firstName=newFn, lastName=ln)

Ref: https://stackoverflow.com/a/36716023/1377058

If you do not want to add dozer as a dependency, you can also use BeanUtils to copy only those properties that are not null. Take a look at this question for details on how to do it.

Ranjith
  • 1,623
  • 3
  • 21
  • 34
  • 1
    I would not recommends Dozer as the performances are quite poor compared to other mapping libs like MapStruct or Selma which looks to be better. – рüффп Jun 22 '18 at 10:47
0

I found that the exact requirement of my question can't be fulfilled. Dozer Bean Mapper seemed like an option but it's more about mapping bean objects of different classes where we can correspond the fields to be mapped which is not my case.

The solution was to alter the approach wherein all the required information are sent while doing an update.

As Simon pointed out in the comment:

Simple answer: that's not possible. Because after deserialization if fields are missing the value is null. Java is a static typed language and does not fit very well with your requirments.

Sudip Bhandari
  • 2,165
  • 1
  • 27
  • 26
0

This has been a big disappointment with Spring Boot JPA, and to think that in NodeJs I only do the following:

User.query().findById(id).patch({name: 'Peter'})

In SpringBoot I have seen solutions that need to install libraries, 100 lines of code, very disappointed!