1. 概述
在本快速教程中,我們將探討 HTTP PUT 和 PATCH 請求方法之間的差異,以及這兩種操作的語義。
我們將使用 Spring 實現兩個 REST 端點,以支持這兩種操作,從而更好地理解它們的差異以及正確的使用方法。
2. 何時使用 PUT 以及何時使用 PATCH?
我們先從一個簡單和一個稍複雜的陳述開始。
當客户端需要完全替換現有資源時,他們可以使用 PUT。當他們進行部分更新時,他們可以使用 HTTP PATCH。
例如,當更新資源的單個字段時,發送完整的資源表示形式可能既繁瑣又浪費了大量不必要的帶寬。在這種情況下,PATCH 的語義更合理。
這裏需要考慮的重要方面是 冪等性。PUT 是冪等的;PATCH 可以是冪等的,但並非必需。因此,根據我們正在實施的操作的語義,我們可以根據這一特性選擇一個或另一個。
3. 實現 PUT 和 PATCH 邏輯
假設我們要實現更新 HeavyResource資源的 REST API,並且需要更新多個字段:
public class HeavyResource {
private Integer id;
private String name;
private String address;
// ...
}
首先,我們需要創建處理資源完整更新的端點,使用 PUT 方法:
@PutMapping("/heavyresource/{id}")
public ResponseEntity<?> saveResource(@RequestBody HeavyResource heavyResource,
@PathVariable("id") String id) {
heavyResourceRepository.save(heavyResource, id);
return ResponseEntity.ok("resource saved");
}
這是一個標準的更新資源的端點。
現在假設地址字段經常由客户端更新。在這種情況下,我們不希望發送整個 HeavyResource對象以及所有字段,但我們仍然希望能夠僅更新 address字段——通過 PATCH 方法。
我們可以創建一個 HeavyResourceAddressOnlyDTO 來表示地址字段的局部更新:
public class HeavyResourceAddressOnly {
private Integer id;
private String address;
// ...
}
接下來,我們可以利用 PATCH 方法發送局部更新:
@PatchMapping("/heavyresource/{id}")
public ResponseEntity<?> partialUpdateName(
@RequestBody HeavyResourceAddressOnly partialUpdate, @PathVariable("id") String id) {
heavyResourceRepository.save(partialUpdate, id);
return ResponseEntity.ok("resource address updated");
}
通過這種更細粒度的 DTO,我們可以只發送需要更新的字段,而無需發送整個 HeavyResource。
如果有很多這些局部更新操作,我們還可以跳過為每個操作創建自定義 DTO,只使用一個 map:
@RequestMapping(value = "/heavyresource/{id}", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> partialUpdateGeneric(
@RequestBody Map<String, Object> updates,
@PathVariable("id") String id) {
heavyResourceRepository.save(updates, id);
return ResponseEntity.ok("resource updated");
}
這個解決方案將為我們提供更大的 API 實現靈活性,但我們也會失去一些東西,例如驗證。
4. 測試 PUT 和 PATCH
最後,我們來編寫測試,用於這兩個 HTTP 方法。
首先,我們想通過 PUT 方法測試資源的完整更新:
mockMvc.perform(put("/heavyresource/1")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(
new HeavyResource(1, "Tom", "Jackson", 12, "heaven street")))
).andExpect(status().isOk());
通過使用 PATCH 方法可以實現部分更新的執行:
mockMvc.perform(patch("/heavyrecource/1")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(
new HeavyResourceAddressOnly(1, "5th avenue")))
).andExpect(status().isOk());
我們還可以編寫一個更通用的測試:
HashMap<String, Object> updates = new HashMap<>();
updates.put("address", "5th avenue");
mockMvc.perform(patch("/heavyresource/1")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(updates))
).andExpect(status().isOk());
5. 使用 Null 值處理部分請求
當我們編寫 PATCH 方法的實現時,我們需要指定如何處理在 HeavyResourceAddressOnly 中收到 Null 值作為 address 字段值的方案。假設客户端發送以下請求:
{
"id" : 1,
"address" : null
}
那麼我們可以將其處理為將 address 字段的值設置為 null 或通過將其視為無更改來忽略此類請求。
我們應該為處理 null 選擇一種策略,並在每個 PATCH 方法實現中堅持使用該策略。
6. 結論
在本文中,我們重點介紹了 HTTP PATCH 和 PUT 方法之間的差異。
我們實施了一個簡單的 Spring REST 控制器,通過 PUT 方法更新資源,並使用 PATCH 進行部分更新。