首页 文章

Spring部分更新对象数据绑定

提问于
浏览
20

我们正在尝试在Spring 3.2中实现一个特殊的部分更新功能 . 我们使用Spring作为后端,并有一个简单的Javascript前端 . 我无法找到满足我们要求的直接解决方案,即update()函数应该采用任意数量的字段:值并相应地更新持久性模型 .

我们对所有字段进行内联编辑,因此当用户编辑字段并确认时,id和修改后的字段将作为json传递给控制器 . 控制器应该能够从客户端(1到n)接收任意数量的字段并仅更新这些字段 .

例如,当id == 1的用户编辑他的displayName时,发布到服务器的数据如下所示:

{"id":"1", "displayName":"jim"}

目前,我们在UserController中有一个不完整的解决方案,如下所述:

@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);
    ...
}

这里的代码有效,但有一些问题: @RequestBody 创建一个新的 updateUser ,填入 iddisplayName . CustomObjectMerger 将此 updateUser 与数据库中相应的 dbUser 合并,更新 updateUser 中包含的唯一字段 .

问题是Spring使用默认值和其他自动生成的字段值填充 updateUser 中的某些字段,这些字段值在合并时会覆盖 dbUser 中的有效数据 . 明确声明它应该忽略这些字段不是一个选项,因为我们希望我们的 update 能够设置这些字段 .

我正在寻找一些方法让Spring自动合并显式发送到 update() 函数的信息到 dbUser (不重置默认/自动字段值) . 有没有简单的方法来做到这一点?

Update: 我've already considered the following option which does almost what I'米要求,但不完全 . 问题是它将更新数据作为 @RequestParam 并且(AFAIK)不执行JSON字符串:

//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){
....
}

我已经考虑重新编写我的 customObjectMerger() 以更合适地使用JSON,计算并仅考虑来自 HttpServletRequest 的字段 . 但是,即使不得不首先使用 customObjectMerger() ,当 Spring 天提供的几乎正是我正在寻找的东西时,也会感到hacky,减去缺少的JSON功能 . 如果有人知道如何让Spring做到这一点,我将非常感激!

6 回答

  • 0

    我刚遇到同样的问题 . 我目前的解决方案是这样的 . 我还没有做太多的测试,但是在初步检查时,它看起来工作得相当好 .

    @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);
    }
    

    ObjectMapper是一个类型为org.codehaus.jackson.map.ObjectMapper的bean .

    希望这有助于某人,

    Edit:

    遇到了与子对象有关的问题 . 如果子对象收到要部分更新的属性,则会创建一个新对象,更新该属性并进行设置 . 这会擦除该对象上的所有其他属性 . 如果我遇到一个干净的解决方案,我会更新 .

  • 19

    我们正在使用@ModelAttribute来实现您想要做的事情 .

    • 创建一个使用@ modelattribute注释的方法,该方法基于存储库的路径变量加载用户 .

    • 使用param @modelattribute创建方法@Requestmapping

    这里的要点是@modelattribute方法是模型的初始化器 . 然后spring将请求与此模型合并,因为我们在@requestmapping方法中声明了它 .

    这为您提供了部分更新功能 .

    一些,甚至很多? ;)因为我们直接在控制器中使用DAO并且不在专用服务层中进行合并,所以认为这是不好的做法 . 但目前我们没有因为这种方法而遇到问题 .

  • 0

    我构建了一个API,在调用persiste或merge或update之前将视图对象与实体合并 .

    这是第一版,但我认为这是一个开始 .

    只需在POJO`S字段中使用注释UIAttribute,然后使用:

    MergerProcessor.merge(pojoUi, pojoDb);

    它适用于本机属性和集合 .

    git:https://github.com/nfrpaiva/ui-merge

  • 1

    可以使用以下方法 .

    对于这种情况,PATCH方法会更合适,因为实体将被部分更新 .

    在控制器方法中,将请求主体作为字符串 .

    将该String转换为JSONObject . 然后迭代密钥并使用传入数据更新匹配变量 .

    import org.json.JSONObject;
    
    @RequestMapping(value = "/{id}", method = RequestMethod.PATCH )
    public ResponseEntity<?> updateUserPartially(@RequestBody String rawJson, @PathVariable long id){
    
        dbUser = userRepository.findOne(id);
    
        JSONObject json = new JSONObject(rawJson);
    
        Iterator<String> it = json.keySet().iterator();
        while(it.hasNext()){
            String key = it.next();
            switch(key){
                case "displayName":
                    dbUser.setDisplayName(json.get(key));
                    break;
                case "....":
                    ....
            }
        }
        userRepository.save(dbUser);
        ...
    }
    

    这种方法的缺点是,您必须手动验证传入的值 .

  • 1

    主要问题在于您的以下代码:

    @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);
        ...
    }
    

    在上面的函数中,您可以调用一些私有函数和类(userRepository,customObjectMerger,...),但不解释它是如何工作的或这些函数的外观 . 所以我只能猜测:

    CustomObjectMerger将此updateUser与数据库中相应的dbUser合并,更新updateUser中包含的唯一字段 .

    在这里,我们不是你的功能,你没有展示它 . 但是根据你的描述,我可以猜测:你将 updateUser 中的所有属性复制到数据库中的对象 . 这绝对是一种错误的方式, since when Spring map the object, it will fill all the data . 而且您只想更新一些特定属性 .

    您的案例有两种选择:

    1)将所有属性(包括未更改的属性)发送到服务器 . 这可能会花费更多的带宽,但您仍然保持自己的方式

    2)您应该设置一些特殊值作为User对象的默认值(例如,id = -1,age = -1 ...) . 然后在customObjectMerger中,您只需设置不是-1的值 .

    如果您觉得上述2个解决方案不满意,请考虑自己解析json请求,而不要打扰Spring对象映射机制 . 有时它只是混淆了很多 .

  • 1

    部分更新可以通过使用 @SessionAttributes 功能来解决,这些功能可以用 customObjectMerger 做你自己做的事情 .

    看看我的答案,特别是编辑,让你开始:

    https://stackoverflow.com/a/14702971/272180

相关问题