在 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 设计和良好的资源设计通常看起来非常不同。我知道许多框架会驱使你朝那个方向发展,但如果你还没有认真考虑过将它们解耦,你可能想要这样做。
我即将在 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 设计和良好的资源设计通常看起来非常不同。我知道许多框架会驱使你朝那个方向发展,但如果你还没有认真考虑过将它们解耦,你可能想要这样做。