Spring MVC PATCH method: partial updates

42,566

Solution 1

You may change boolean to Boolean and assign null value for all fields that you don't want to update. The only one not null value will define you which field client want to update.

Solution 2

This could be very late, but for the sake of newbies and people who encounter the same problem, let me share you my own solution.

In my past projects, to make it simple, I just use the native java Map. It will capture all the new values including the null values that the client explicitly set to null. At this point, it will be easy to determine which java properties needs to be set as null, unlike when you use the same POJO as your domain model, you won't be able to distinguish which fields are set by the client to null and which are just not included in the update but by default will be null.

In addition, you have to require the http request to send the ID of the record you want to update, and do not include it on the patch data structure. What I did, is set the ID in the URL as path variable, and the patch data as a PATCH body.Then with the ID, you would get first the record via a domain model,then with the HashMap, you can just use a mapper service or utility to patch the changes to the concerned domain model.

Update

You can create a abstract superclass for your services with this kind of generic code, you must use Java Generics. This is just a segment of possible implementation, I hope you get the idea.Also it is better to use mapper framework such as Orika or Dozer.

public abstract class AbstractService<Entity extends BaseEntity, DTO extends BaseDto> {
    @Autowired
    private MapperService mapper;

    @Autowired
    private BaseRepo<Entity> repo;

    private Class<DTO> dtoClass;

    private Class<Entity> entityCLass;

    public AbstractService(){
       entityCLass = (Class<Entity>) SomeReflectionTool.getGenericParameter()[0];
       dtoClass = (Class<DTO>) SomeReflectionTool.getGenericParameter()[1];
    }

    public DTO patch(Long id, Map<String, Object> patchValues) {
        Entity entity = repo.get(id);
        DTO dto = mapper.map(entity, dtoClass);
        mapper.map(patchValues, dto);
        Entity updatedEntity = toEntity(dto);
        save(updatedEntity);
        return dto;
    }
}

Solution 3

The correct way to do this is the way proposed in JSON PATCH RFC 6902

A request example would be:

PATCH http://example.com/api/entity/1 HTTP/1.1
Content-Type: application/json-patch+json 

[
  { "op": "replace", "path": "aBoolean", "value": true }
]

Solution 4

After digging around a bit I found an acceptable solution using the same approach currently used by a Spring MVC DomainObjectReader see also: JsonPatchHandler

import org.springframework.data.rest.webmvc.mapping.Associations

@RepositoryRestController
public class BookCustomRepository {
    private final DomainObjectReader domainObjectReader;
    private final ObjectMapper mapper;

    private final BookRepository repository;


    @Autowired
    public BookCustomRepository(BookRepository bookRepository, 
                                ObjectMapper mapper,
                                PersistentEntities persistentEntities,
                                Associations associationLinks) {
        this.repository = bookRepository;
        this.mapper = mapper;
        this.domainObjectReader = new DomainObjectReader(persistentEntities, associationLinks);
    }


    @PatchMapping(value = "/book/{id}", consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<?> patch(@PathVariable String id, ServletServerHttpRequest request) throws IOException {

        Book entityToPatch = repository.findById(id).orElseThrow(ResourceNotFoundException::new);
        Book patched = domainObjectReader.read(request.getBody(), entityToPatch, mapper);
        repository.save(patched);

        return ResponseEntity.noContent().build();
    }

}

Solution 5

The whole point of PATCH is that you are not sending the entire entity representation, so I don't understand your comments about the empty string. You would have to handle some sort of simple JSON such as:

{ aBoolean: true }

and apply that to the specified resource. The idea is that what has been received is a diff of the desired resource state and the current resource state.

Share:
42,566
mael
Author by

mael

French (parisian, no one's perfect) Java/JEE developer. =)

Updated on September 14, 2021

Comments

  • mael
    mael over 2 years

    I have a project where I am using Spring MVC + Jackson to build a REST service. Let's say I have the following java entity

    public class MyEntity {
        private Integer id;
        private boolean aBoolean;
        private String aVeryBigString;
        //getter & setters
    }
    

    Sometimes, I just want to update the boolean value, and I don't think that sending the whole object with its big string is a good idea just to update a simple boolean. So, I have considered using the PATCH HTTP method to only send the fields that need to be updated. So, I declare the following method in my controller:

    @RequestMapping(method = RequestMethod.PATCH)
    public void patch(@RequestBody MyVariable myVariable) {
        //calling a service to update the entity
    }
    

    The problem is: how do I know which fields need to be updated? For instance, if the client just wants to update the boolean, I will get an object with an empty "aVeryBigString". How am I supposed to know that the user just wants to update the boolean, but does not want to empty the string?

    I have "solved" the problem by building custom URLs. For instance, the following URL: POST /myentities/1/aboolean/true will be mapped to a method that allows to only update the boolean. The problem with this solution is that it is not REST compliant. I don't want to be 100% REST compliant, but I do not feel comfortable with providing a custom URL to update each field (especially given that it causes problems when I want to update several fields).

    Another solution would be to split "MyEntity" into multiple resources and just update these resources, but I feel like it does not make sense: "MyEntity" is a plain resource, it is not composed of other resources.

    So, is there an elegant way of solving this problem?