Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Partial Update Object Data Binding

Tags:

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!

like image 493
Sam Avatar asked Feb 27 '13 23:02

Sam


2 Answers

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.

like image 170
Tyler Eastman Avatar answered Jan 04 '23 23:01

Tyler Eastman


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.

like image 38
Martin Frey Avatar answered Jan 05 '23 00:01

Martin Frey