弹簧MVC补丁方法:部分更新

2022-08-31 15:44:39

我有一个项目,我正在使用Spring MVC + Jackson来构建REST服务。假设我有以下 java 实体

public class MyEntity {
    private Integer id;
    private boolean aBoolean;
    private String aVeryBigString;
    //getter & setters
}

有时,我只想更新布尔值,我不认为发送整个对象及其大字符串只是更新一个简单的布尔值是一个好主意。因此,我考虑使用 PATCH HTTP 方法仅发送需要更新的字段。因此,我在控制器中声明以下方法:

@RequestMapping(method = RequestMethod.PATCH)
public void patch(@RequestBody MyVariable myVariable) {
    //calling a service to update the entity
}

问题是:我如何知道哪些字段需要更新?例如,如果客户端只想更新布尔值,我将得到一个带有空“aVeryBigString”的对象。我怎么知道用户只想更新布尔值,但不想清空字符串?

我已经通过构建自定义URL“解决了”这个问题。例如,以下 URL:POST /myentities/1/aboolean/true 将映射到仅允许更新布尔值的方法。此解决方案的问题在于它不符合 REST 标准。我不想100%符合REST,但我对提供自定义URL来更新每个字段感到不舒服(特别是考虑到当我想更新多个字段时它会导致问题)。

另一种解决方案是将“MyEntity”拆分为多个资源,然后只更新这些资源,但我觉得这没有意义:“MyEntity”一个普通的资源,它不是由其他资源组成的

那么,有没有一种优雅的方法来解决这个问题呢?


答案 1

这可能很晚了,但为了新手和遇到同样问题的人,让我分享我自己的解决方案。

在我过去的项目中,为了简单起见,我只使用原生java Map。它将捕获所有新值,包括客户端显式设置为 null 的 null 值。此时,很容易确定哪些 java 属性需要设置为 null,这与使用与域模型相同的 POJO 时不同,您将无法区分客户端将哪些字段设置为 null,哪些字段只是未包含在更新中,但默认情况下将为 null。

此外,您必须要求 http 请求发送要更新的记录的 ID,并且不要将其包含在修补程序数据结构中。我所做的是将URL中的ID设置为路径变量,并将补丁数据设置为PATCH正文。然后使用ID,您将首先通过域模型获取记录,然后使用HashMap,您只需使用映射器服务或实用程序来修补对相关域模型的更改。

更新

您可以使用这种泛型代码为服务创建抽象超类,必须使用 Java 泛型。这只是可能实现的一部分,我希望你明白了。此外,最好使用映射器框架,如Orika或Dozer。

public abstract class AbstractService<Entity extends BaseEntity, DTO extends BaseDto> {
    @Autowired
    private MapperService mapper;

    @Autowired
    private BaseRepo<Entity> repo;

    private Class<DTO> dtoClass;

    private Class<Entity> entityCLass;

    public AbstractService(){
       entityCLass = (Class<Entity>) SomeReflectionTool.getGenericParameter()[0];
       dtoClass = (Class<DTO>) SomeReflectionTool.getGenericParameter()[1];
    }

    public DTO patch(Long id, Map<String, Object> patchValues) {
        Entity entity = repo.get(id);
        DTO dto = mapper.map(entity, dtoClass);
        mapper.map(patchValues, dto);
        Entity updatedEntity = toEntity(dto);
        save(updatedEntity);
        return dto;
    }
}

答案 2

正确的方法是JSON PATCH RFC 6902中提出的方法。

请求示例如下:

PATCH http://example.com/api/entity/1 HTTP/1.1
Content-Type: application/json-patch+json 

[
  { "op": "replace", "path": "aBoolean", "value": true }
]