2016-10-01 30 views
6

我即將開始在Java中使用新的rest api進行開發。 我的問題是關於使用PATCH - 爲什麼?在java REST API中,使用PATCH與PUT來更新實體

比方說,我們有一個名爲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 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歲),就不會失敗。


代替PATCH,如果我使用了PUT,客戶端需要發送完整的地址對象。 這樣做,我可以使用@Valid,以確保該地址的確是有效的


。如果人們的前提是GET時,必須進行做任何更新之前,爲什麼不使用一個PUT優勢通過PATCH? 我錯過了什麼嗎?

除了

我的結論是,使用動態類型語言的開發人員正在使用PATCH,因爲我看不到任何的好處是用它從一個靜態類型語言線Java或C#的支持者。它似乎增加了更多的複雜性。

+2

似乎狀態有效,PATCH請求你的數據庫查詢後,應顯示行:「可選地址行2」,因爲你正在檢查,partialAddress.getLine2()!= NULL。 – kuhajeyan

+0

補丁請求應該包含由客戶端計算的指令,服務器可以使用該指令將某些資源的狀態A轉換爲狀態B,而不僅僅是簡化的部分更新。進一步閱讀:[SO文檔](http://stackoverflow.com/documentation/http/3423/http-for-apis/11812/edit-a-resource#t=201610031245323472567)和[好博客文章](http:///williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/) –

+0

這是一個很好的問題。關於line2中的null,恰恰是因爲您手動映射了字段並檢查非空值以覆蓋。目前,我正在使用[Dozer](http://dozer.sourceforge.net/documentation/usage.html)的項目進行工作,您可以選擇映射空值。無論如何,我們使用POST來添加/創建資源,並使用PUT來修改它們,但使用此映射器,它可以執行部分​​映射。所以我辯論如果這是首選的方式,或者我應該使用POST(創建),PUT(完全更新),PATCH(部分更新)。 –

回答

7

使用PATCH上傳現有對象的修改版本幾乎總是正是你所概述的原因問題。如果您想用JSON使用PATCH,我強烈建議您使用或者按照或RFC 7396。我不會跟7396說話,因爲我對它不熟悉,但是按照6902,你會爲PATCH操作定義一個單獨的資源。在你給的例子,它看起來像:

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

你可以這樣處理此,使得該開始在當前服務器狀態和應用在PATCH變化的新的實體對象。對新實體對象運行驗證。如果通過,將其推送到數據層。如果失敗,則返回錯誤代碼。

如果PUT不會增加太多的開銷,這是一個好主意。冪等性是一件好事。權衡是你通過電線推送更多數據。如果你的資源不大並且不經常訪問,那可能不是什麼大不了的事情。如果您的資源很大並且經常訪問,那可能會增加大量開銷。當然,我們不能告訴你轉折點。

您似乎也已將您的資源模型完全綁定到您的數據庫模型。好的數據庫表格設計和良好的資源設計對於非平凡的項目通常看起來非常不同。我知道很多框架都會推動你朝着這個方向發展,但是如果你沒有認真考慮將它們解耦,你可能會想。

+0

我用SpringDataRest看到的例子讓我覺得這是一個簡單的JSON負載,但是您列出的請求格式對我更有意義。如果我使用我的@service作爲事務邊界,那麼這個Address dbData = getId()並且在頂部應用更改可能需要在ServiceImpl中(而不是在控制器中)發生。有趣的答案。但是現在將繼續討論這個問題來看看更多的建議。 – SGB