Spring Partial Update Object Data Binding
We are trying to implement a special partial update function in Spring 3.2. We are using Spring for the backend and have a simple Javascript frontend. I've not been able to find a straight-forward solution to our requirements, which is The update() function should take in any number of field:values and update the persistence model accordingly.
We have in-line editing for all of our fields, so that when the user edits a field and confirms, an id and the modified field get passed to the controller as json. The controller should be able to take in any number of fields from the client (1 to n) and update only those fields.
e.g., when a user with id==1 edits his displayName, the data posted to the server looks like this:
{"id":"1", "displayName":"jim"}
Currently, we have an incomplete solution in the UserController as outlined below:
@RequestMapping(value = "/{id}", method = RequestMethod.POST )
public @ResponseBody ResponseEntity<User> update(@RequestBody User updateUser) {
dbUser = userRepository.findOne(updateUser.getId());
customObjectMerger(updateUser, dbUser);
userRepository.saveAndFlush(updateUuser);
...
}
The code here works, but has some issues: The @RequestBody
creates a new updateUser
, fills in the id
and the displayName
. CustomObjectMerger
merges this updateUser
with the corresponding dbUser
from the database, updating the only fields included in updateUser
.
The problem is that Spring populates some fields in updateUser
with default values and other auto-generated field values, which, upon merging, overwrites valid data that we have in dbUser
. Explicitly declaring that it should ignore these fields is not an option, as we want our update
to be able to set these fields as well.
I am looking into some way to have Spring automatically merge ONLY the information explicitly sent into the update()
function into the dbUser
(without resetting default/auto field values). Is there any simple way to do this?
Update: I've already considered the following option which does almost what I'm asking for, but not quite. The problem is that it takes update data in as @RequestParam
and (AFAIK) doesn't do JSON strings:
//load the existing user into the model for injecting into the update function
@ModelAttribute("user")
public User addUser(@RequestParam(required=false) Long id){
if (id != null) return userRepository.findOne(id);
return null;
}
....
//method declaration for using @MethodAttribute to pre-populate the template object
@RequestMapping(value = "/{id}", method = RequestMethod.POST )
public @ResponseBody ResponseEntity<User> update(@ModelAttribute("user") User updateUser){
....
}
I've considered re-writing my customObjectMerger()
to work more appropriately with JSON, counting and having it take into consideration only the fields coming in from HttpServletRequest
. but even having to use a customObjectMerger()
in the first place feels hacky when spring provides almost exactly what I am looking, minus the lacking JSON functionality. If anyone knows of how to get Spring to do this, I'd greatly appreciate it!
Solution 1:
I've just run into this same problem. My current solution looks like this. I haven't done much testing yet, but upon initial inspection it looks to be working fairly well.
@Autowired ObjectMapper objectMapper;
@Autowired UserRepository userRepository;
@RequestMapping(value = "/{id}", method = RequestMethod.POST )
public @ResponseBody ResponseEntity<User> update(@PathVariable Long id, HttpServletRequest request) throws IOException
{
User user = userRepository.findOne(id);
User updatedUser = objectMapper.readerForUpdating(user).readValue(request.getReader());
userRepository.saveAndFlush(updatedUser);
return new ResponseEntity<>(updatedUser, HttpStatus.ACCEPTED);
}
The ObjectMapper is a bean of type org.codehaus.jackson.map.ObjectMapper.
Hope this helps someone,
Edit:
Have run into issues with child objects. If a child object receives a property to partially update it will create a fresh object, update that property, and set it. This erases all the other properties on that object. I'll update if I come across a clean solution.
Solution 2:
We are using @ModelAttribute to achive what you want to do.
Create a method annotated with@modelattribute which loads a user based on a pathvariable throguh a repository.
create a method @Requestmapping with a param @modelattribute
The point here is that the @modelattribute method is the initializer for the model. Then spring merges the request with this model since we declare it in the @requestmapping method.
This gives you partial update functionality.
Some , or even alot? ;) would argue that this is bad practice anyway since we use our DAOs directly in the controller and do not do this merge in a dedicated service layer. But currently we did not ran into issues because of this aproach.
Solution 3:
I build an API that merge view objects with entities before call persiste or merge or update.
It's a first version but I think It's a start.
Just use the annotation UIAttribute in your POJO`S fields then use:
MergerProcessor.merge(pojoUi, pojoDb);
It works with native Attributes and Collection.
git: https://github.com/nfrpaiva/ui-merge