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

时间:2016-10-01 14:50:12

标签: java spring rest api backend

我即将开始使用Java开发一个新的rest 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

相应的@RestController AddressResource.java将使用此方法:

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

@valid将确保在将数据存储到表中之前实体有效。

现在假设,我从上面的公寓搬到街上的房子里。如果我使用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)
}

现在,如果您查询表格,我的地址是否胜出?

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

可以看出,上述更新导致line2的值不正确。 这是因为在java中,在实例化类时,Address类中的所有实例变量都被初始化为null(如果它们是基元,则为默认初始值)。因此无法区分line2被更改为null与默认值。

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

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

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

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

用户不小心打了190(当他们真的意味着19年)时,它不会失败。

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

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

除了

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

1 个答案:

答案 0 :(得分:7)

使用PATCH上传现有对象的修改版本几乎总是存在问题,原因完全是您概述的原因。如果您想将PATCH与JSON一起使用,我强烈建议您关注RFC 6902RFC 7396。我不会和7396说话,因为我对它并不熟悉,但是为了遵循6902,你将为PATCH操作定义一个单独的资源。在您给出的示例中,它看起来像:

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

然后,您将处理此问题,生成一个以当前服务器状态启动的新实体对象,并在PATCH中应用更改。对新实体对象运行验证。如果通过,则将其推送到数据层。如果失败,则返回错误代码。

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

您似乎也已将资源模型完全绑定到数据库模型。对于非平凡的项目,良好的数据库表设计和良好的资源设计通常看起来非常不同。我知道很多框架会让你朝这个方向发展,但如果你没有认真考虑将它们分离,你可能会想要。