qwertyqwerty
qwertyqwerty

Reputation: 235

Spring boot - partial update best practise?

I am using Spring boot v2 with mongo database. I was wondering what is the best way to do partial updates on the data model. Say I have a model with x attributes, depending on the request I may only want to update 1, 2, or x of them attributes. Should I be exposing an endpoint for each type of update operation, or is it possible to expose one endpoint and do it in a generic way? Note I will need to be able to validate the contents of the request attributes (e.g tel. no. must be numbers only)

Thanks,

Upvotes: 4

Views: 10491

Answers (5)

Ulises CT
Ulises CT

Reputation: 1467

It's pretty easy. I'll give you a nice example.

Let's say you have an User entity as follows:

public class User {

    private String name;
    private String surname;
    private String email;
    private String phoneNumber;

    // more properties, getters, etc.
}

Create a POJO that represents the partial update you wanna do over this entity. It will only contain the fields you wanna allow to be partially updated. In this case just name and surname:

public class UserUpdateRequest {

    private String name;
    private String surname;
}

Create a PATCH endpoint for the entity you wanna partially update. Lets say /users/{userId}

@PatchMapping("users/{userId}")
public ResponseEntity<?> updateUser(
       @PathVariable Long userId,
       @RequestBody UserUpdateRequest userUpdateRequest) {

  User updatedUser = userService.updateUser(userId, userUpdateRequest);
  return ResponseEntity.ok(updatedUser);
}

Now in the service just retrieve the user you wanna update. And next you have 2 choices. Either check with ifs which field in userUpdateRequest is not null and then update it in the user to be updated or either use some library like MapStruct or your library of choice to do all this boilerplate code for you. You just have to create a mapper which will map from userUpdateRequest to updatedUser ignoring the null fields from userUpdatedRequest. In this example I'll use the if approach which of course I don't recommend you, it's just to show you how to do it and because the library configuration is different on each library. But once again: use a mapping library.

@Transactional
public User updateUser(Long userId, UserUpdateRequest userUpdateRequest) {
    User user = userRepository.findById(userId);

    if (userUpdateRequest.getName() != null) {
        user.setName(userUpdateRequest.getName());
    }
    if (userUpdateRequest.getSurname() != null) {
        user.setSurname(userUpdateRequest.getSurname());
    }
    return userRepository.save(user);
}

Enjoy =)

Upvotes: 0

Youans
Youans

Reputation: 5061

If you are using Mapstruct then this blog is very helpful https://cassiomolin.com/2019/06/10/using-http-patch-in-spring/

the idea is simple,

  • You load the entity from DB
  • You convert that entity to dto same type as the payload sent in the API request
  • Then you apply merge/patch operation on the payload & the converted dto from last step [utlity included]
  • The output is merged dto
  • You convert that merged dto to the entity
  • You save the entity

example from the blog:

@PatchMapping(path = "/{id}", consumes = "application/json-patch+json")
public ResponseEntity<Void> updateContact(@PathVariable Long id,
                                          @RequestBody JsonPatch patchDocument) {

    // Find the domain model that will be patched
    Contact contact = contactService.findContact(id).orElseThrow(ResourceNotFoundException::new);
    
    // Map the domain model to an API resource model
    ContactResourceInput contactResource = contactMapper.asContactResourceInput(contact);
    
    // Apply the patch to the API resource model
    ContactResourceInput contactResourcePatched = patch(patchDocument, contactResource, ContactResourceInput.class);

     // Update the domain model with the details from the API resource model
    contactMapper.update(contactResourcePatched, contact);
    
    // Persist the changes
    contactService.updateContact(contact);

    // Return 204 to indicate the request has succeeded
    return ResponseEntity.noContent().build();
}

P.S Using mapstruct you will have an easy control on properties mapping, map something to something else or even ignore, this is beneficial if you have a customerId long type and you want to map it to customer

Upvotes: 0

Farai Mugaviri
Farai Mugaviri

Reputation: 103

You can actually expose just one endpoint. This is the situation I had a few months ago:

I wanted people to modify any (or even all)fields of a Projects document (who am I to force the users to manually supply all fields lol). So I have my Model, Project.java:

package com.foxxmg.jarvisbackend.models;
//imports

@Document(collection = "Projects")
public class Project {
    @Id
    public String id;
    public String projectTitle;
    public String projectOverview;
    public Date startDate;
    public Date endDate;
    public List<String> assignedTo;
    public String progress;

   //constructors

    //getters & setters
}

I have my repository:

ProjectRepository.java

 package com.foxxmg.jarvisbackend.repositories;
//imports

    @Repository
    public interface ProjectRepository extends MongoRepository<Project, String>, QuerydslPredicateExecutor<Project> {
//please note, we are going to use findById(string) method for updating
        Project findByid(String id);
//other abstract methods 


    }

Now to my Controller, ProjectController.java:

package com.foxxmg.jarvisbackend.controllers;
        //import

        @RestController
        @RequestMapping("/projects")
        @CrossOrigin("*")
        public class ProjectController {
            @Autowired
            private ProjectRepository projectRepository;

    @PutMapping("update/{id}")
        public ResponseEntity<Project> update(@PathVariable("id") String id, @RequestBody Project project) {
            Optional<Project> optionalProject = projectRepository.findById(id);
            if (optionalProject.isPresent()) {
                Project p = optionalProject.get();
                if (project.getProjectTitle() != null)
                    p.setProjectTitle(project.getProjectTitle());
                if (project.getProjectOverview() != null)
                    p.setProjectOverview(project.getProjectOverview());
                if (project.getStartDate() != null)
                    p.setStartDate(project.getStartDate());
                if (project.getEndDate() != null)
                    p.setEndDate(project.getEndDate());
                if (project.getAssignedTo() != null)
                    p.setAssignedTo(project.getAssignedTo());
                return new ResponseEntity<>(projectRepository.save(p), HttpStatus.OK);
            } else
                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
     }

That will allow partial update in MongoDB with Spring Boot.

Upvotes: 0

Downhillski
Downhillski

Reputation: 2655

If you are using Spring Data MongoDB, you have two options either use the MongoDB Repository or using the MongoTemplate.

Upvotes: -3

bayrem404
bayrem404

Reputation: 76

HTTP PATCH is a nice way to update a resource by specifying only the properties that have changed. The following blog explain it very well

Upvotes: 1

Related Questions