【问题标题】:How to handle partial updates to an Entity in Spring Boot如何在 Spring Boot 中处理对实体的部分更新
【发布时间】:2021-04-14 19:44:44
【问题描述】:

我们正在使用 Spring Boot 对实体进行部分更新,如下所示,是否有更优雅的方式,好像实体包含 50 多个属性,然后处理起来真的很痛苦

public Foo updateFoo(@PathVariable String id, @RequestBody Foo fooInput) {
    Foo foo = fooRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));

    if (fooInput.getCaption() != null) {
        foo.setCaption(fooInput.getCaption());
    }   

    if (fooInput.getBar() != null) {
        foo.setBar(fooInput.getBar());
    }   
    ...
}  

【问题讨论】:

  • 也许你要找的是一个DTO-Model转换工具,你可以找到modelmapperhere的介绍。

标签: spring spring-boot


【解决方案1】:

您可以在 Spring Framework 中使用 JSON Patch 库。 Implementing JSON Patch in a Spring Boot Application

这是用 Java 编写的 RFC 6902(JSON 补丁)和 RFC 7386(JSON 合并补丁)的实现,其核心使用 Jackson (2.2.x)。

JSON Patch 是一种描述 JSON 文档更改的格式。它可用于避免在仅部分更改时发送整个文档。当与 HTTP PATCH 方法结合使用时,它允许以符合标准的方式对 HTTP API 进行部分更新。补丁文档本身就是 JSON 文档。 JSON Patch 在 IETF 的 RFC 6902 中指定。

【讨论】:

    【解决方案2】:

    如果 JSON 中不存在未更新的字段,您可以这样做:

    Foo updateFoo(@PathVariable String id, @RequestBody String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        
        Foo foo = fooRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));
    
        ObjectReader objectReader = mapper.readerForUpdating(foo);
        
        foo = objectReader.readValue(json);
        return foo;
    }
    

    如果未更新的字段设置为 null(或 JSON 中不存在),您可以使用下面的函数。不幸的是,在使用 Jackson 注释进行反序列化期间,我无法导入空值,因此我自己删除了空值。

    Foo updateFoo(@PathVariable String id, @RequestBody ObjectNode node) throws IOException {
        Iterator<String> i = node.fieldNames();
        while(i.hasNext()) {
            String field = i.next();
            if(node.get(field).isNull()) {
                node.remove(field);
            }
        }
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDefaultPropertyInclusion(Include.NON_EMPTY);
        Foo foo = fooRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));
        ObjectReader objectReader = mapper.readerForUpdating(foo);
        foo = objectReader.readValue(node);
        return foo;
    }
    

    【讨论】:

      【解决方案3】:

      根据 Robert Żyłan 的回答,您可以稍微简化代码。 另外,您可以在此处使用两个之一:Map&lt;String, Object&gt; fieldsString json,因此您的服务方法可能如下所示:

      @Autowired
      private ObjectMapper objectMapper;
      
      @Override
      @Transactional
      public Foo save(long id, Map<String, Object> fields) throws JsonMappingException {
          Foo foo = fooRepository.findById(id)
          .orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));
      
          return objectMapper.updateValue(foo , fields);
      }
      

      作为第二种解决方案,您可以使用Reflection 仅映射已发送的值映射中存在的属性,因此您的服务方法可能如下所示:

      @Override
      @Transactional
      public Foo save(long id, Map<String, Object> fields) {
      
          Foo foo = fooRepository.findById(id)
          .orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));
      
          fields.keySet()
                  .forEach(k -> {
                      Method method = ReflectionUtils.findMethod(LocationProduct.class, "set" + StringUtils.capitalize(k));
                      if (method != null) {
                          ReflectionUtils.invokeMethod(method, foo, fields.get(k));
                      }
                  });
          return foo;
      }
      

      第二种解决方案允许您在映射过程中插入一些额外的业务逻辑,可能是转换或计算等。

      也不同于通过名称查找反射字段Field field = ReflectionUtils.findField(Foo.class, k); 并使其可访问,查找属性的setter 实际上调用setter 方法,该方法可能包含要执行的附加逻辑并防止将值设置为私有属性。

      【讨论】:

        猜你喜欢
        • 2021-03-12
        • 2019-04-06
        • 1970-01-01
        • 2020-07-22
        • 2019-05-15
        • 1970-01-01
        • 2019-03-19
        • 1970-01-01
        • 2022-08-21
        相关资源
        最近更新 更多