Reputation: 235
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
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
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,
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
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
Reputation: 2655
If you are using Spring Data MongoDB, you have two options either use the MongoDB Repository or using the MongoTemplate.
Upvotes: -3