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 對象及其所有字段,但我們確實希望能夠僅更新 地址 字段——通過 PATCH 方法。
我們可以創建一個 HeavyResourceAddressOnly DTO 來表示地址字段的局部更新:
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 中收到 address 字段值為 null 的情況。
假設客户端發送以下請求:
{
"id" : 1,
"address" : null
}然後我們可以通過將 地址 字段的值設置為 null,或者簡單地忽略此類請求,將其視為無更改來處理它。
我們應該為處理 null 採用一種策略,並在每個 PATCH 方法實現中堅持使用該策略。
6. 結論
在本文中,我們重點介紹了 HTTP PATCH 和 PUT 方法之間的差異。
我們通過實現一個簡單的 Spring REST 控制器,來更新資源,利用 PUT 方法進行完整更新,以及利用 PATCH 方法進行部分更新。