在 java REST API 中,使用 PATCH 与 PUT 更新实体

In a java REST API, using PATCH vs PUT to update an entity

我即将在 Java 中开始开发新的休息 api。 我的问题是关于 PATCH 的使用 - 为什么?

比方说,我们有一个名为 Address.java

的实体
public class Address {

    @Id
    private Long id

    @NotNull
    private String line1;

    private String line2;       //optional

    @NotNull
    private String city;

    @NotNull
    private String state;   
}

要创建新地址,我会执行此 http 请求:

POST http://localhost:8080/addresses

请求如下:

{
    "line1" : "mandatory Address line 1",
    "line2" : "optional  Address line 2",
    "city"  : "mandatory City",
    "state" : "cd"
}

假设创建的记录有一个 id 1

对应的@RestControllerAddressResource.java会有这个方法:

@PostMapping(value = "/addresses")
public ResponseEntity<Address> create(@valid Address newAddress) {
    addressRepo.save(newAddress);
}

@valid 将在将数据存储到 table.

之前确保实体有效

现在假设,我从上面的公寓搬到街上的一所房子。如果我使用 PATCH,它会变成

PATCH http://localhost:8080/addresses/1

请求负载:

{
    "line1" : "1234 NewAddressDownTheStreet ST",
    "line2" : null
}

对应的@RestController 方法为:

@PatchMapping(value = "/addresses/{id}")
public ResponseEntity<Address> patchAddress(@PathVariable Long id, Address partialAddress) 
{
    Address dbAddress = addressRepo.findOne(id);
    if (partialAddress.getLine1() != null) {
        dbAddress.setLine1(partialAddress.getLine1());
    }
    if (partialAddress.getLine2() != null) {
        dbAddress.setLine2(partialAddress.getLine2());
    }
    if (partialAddress.getCity() != null) {
        dbAddress.setCity(partialAddress.getCity());
    }
    if (partialAddress.getState() != null) {
        dbAddress.setState(partialAddress.getState());
    }

    addressRepo.save(dbAddress)
}

现在如果你查询table,我的地址不就是吗?

"line1" : "1234 NewAddressDownTheStreet ST",
"line2" : "optional  Address line 2",       <-- INCORRECT. Should be null.
"city"  : "mandatory City",
"state" : "cd"

可以看出,上述更新导致第 2 行的值不正确。 这是因为在 java 中,地址 class 中的所有实例变量在实例化 class 时都被初始化为 null(如果它们是原语,则为默认初始值)。所以没有办法区分line2被改成null和默认值。

问题 1) 是否有解决此问题的标准方法?


另一个缺点是,我不能使用@Valid 注释在入口点验证请求——因为它只是部分。因此,无效数据可能会进入系统。

例如,假设有具有以下定义的附加字段:

@Min(0) 
@Max(100)
private Integer lengthOfResidencyInYears, 

并且用户不小心输入了 190(当他们真正意味着 19 年)时,它不会失败。


如果我使用 PUT 而不是 PATCH,客户端将需要发送完整的地址对象。 这样做的好处是我可以使用@Valid 来确保地址确实有效


如果假设 GET 必须始终在进行任何更新之前完成,那么为什么不使用 PUT 而不是 PATCH? 我错过了什么吗?

一边

我的结论是,使用动态类型语言的开发人员是使用 PATCH 的支持者,因为我看不出从静态类型语言行 Java 或 C# 中使用它有任何好处。它似乎只是增加了更多的复杂性。

使用 PATCH 上传现有对象的修改版本几乎总是有问题,这正是您所概述的原因。如果您想将 PATCH 与 JSON 一起使用,我 强烈 建议您遵循 RFC 6902 or RFC 7396 之一。我不会与 7396 交谈,因为我不太熟悉它,但要遵循 6902,您将为 PATCH 操作定义一个单独的资源。在您给出的示例中,它看起来像:

PATCH http://localhost:8080/addresses/1
[
    { "op": "replace", "path": "/line1", "value": "1234 NewAddressDownTheStreet ST" },
    { "op": "remove", "path": "/line2" }
]

然后您将对其进行处理,创建一个从当前服务器状态开始并应用 PATCH 中的更改的新实体对象。 运行 对新实体对象的验证。如果通过,则将其推送到数据层。如果失败,return 一个错误代码。

如果PUT 不会增加太多开销,这是个好主意。幂等性是一件好事。权衡是您通过网络推送更多数据。如果您的资源不大并且不经常访问,那可能没什么大不了的。如果您的资源很大并且经常访问,那可能会开始增加大量开销。当然,我们不能告诉你临界点。

您似乎也已将资源模型完全绑定到数据库模型。对于重要的项目,良好的数据库 table 设计和良好的资源设计通常看起来非常不同。我知道许多框架会驱使你朝那个方向发展,但如果你还没有认真考虑过将它们解耦,你可能想要这样做。