# 6.1.3 更新服务器上的资源

在编写任何处理 HTTP PUT 或 PATCH 命令的控制器代码之前，应该花点时间考虑一下这个问题：为什么有两种不同的 HTTP 方法来更新资源呢？

虽然 PUT 经常用于更新资源数据，但它实际上是 GET 语义的对立面。GET 请求用于将数据从服务器传输到客户机，而 PUT 请求用于将数据从客户机发送到服务器。

从这个意义上说，PUT 实际上是用于执行大规模替换操作，而不是更新操作。相反，HTTP PATCH 的目的是执行补丁或部分更新资源数据。

例如，假设希望能够更改订单上的地址，我们可以通过 REST API 实现这一点，可以用以下这种方式处理 PUT 请求：

```java
@PutMapping("/{orderId}")
public Order putOrder(@RequestBody Order order) {
    return repo.save(order);
}
```

这可能行得通，但它要求客户端在 PUT 请求中提交完整的订单数据。从语义上讲，PUT 的意思是“把这个数据放到这个 URL 上”，本质上是替换任何已经存在的数据。如果订单的任何属性被省略，该属性的值将被 null 覆盖。甚至订单中的 taco 也需要与订单数据一起设置，否则它们将从订单中删除。

如果 PUT 完全替换了资源数据，那么应该如何处理只进行部分更新的请求？这就是 HTTP PATCH 请求和 Spring 的 @PatchMapping 的好处。可以这样写一个控制器方法来处理一个订单的 PATCH 请求：

```java
@PatchMapping(path="/{orderId}", consumes="application/json")
public Order patchOrder(@PathVariable("orderId") Long orderId, 
     @RequestBody Order patch) {
    
    Order order = repo.findById(orderId).get();
    
    if (patch.getDeliveryName() != null) {
        order.setDeliveryName(patch.getDeliveryName());
    }
    
    if (patch.getDeliveryStreet() != null) {
        order.setDeliveryStreet(patch.getDeliveryStreet());
    }
    
    if (patch.getDeliveryCity() != null) {
        order.setDeliveryCity(patch.getDeliveryCity());
    }
    
    if (patch.getDeliveryState() != null) {
        order.setDeliveryState(patch.getDeliveryState());
    }
    
    if (patch.getDeliveryZip() != null) {
        order.setDeliveryZip(patch.getDeliveryState());
    }
    
    if (patch.getCcNumber() != null) {
        order.setCcNumber(patch.getCcNumber());
    }
    
    if (patch.getCcExpiration() != null) {
        order.setCcExpiration(patch.getCcExpiration());
    }
    
    if (patch.getCcCVV() != null) {
        order.setCcCVV(patch.getCcCVV());
    }
    
    return repo.save(order);
}
```

这里要注意的第一件事是，patchOrder() 方法是用 @PatchMapping 而不是 @PutMapping 来注解的，这表明它应该处理 HTTP PATCH 请求而不是 PUT 请求。

但是 patchOrder() 方法比 putOrder() 方法更复杂一些。这是因为 Spring MVC 的映射注解（包括 @PatchMapping 和 @PutMapping）只指定了方法应该处理哪些类型的请求。这些注解没有规定如何处理请求。尽管 PATCH 在语义上暗示了部分更新，但是可以在处理程序方法中编写实际执行这种更新的代码。

对于 putOrder() 方法，接受订单的完整数据并保存它，这符合 HTTP PUT 的语义。但是为了使 patchMapping() 坚持 HTTP PATCH 的语义，该方法的主体需要更多语句。它不是用发送进来的新数据完全替换订单，而是检查传入订单对象的每个字段，并将任何非空值应用于现有订单。这种方法允许客户机只发送应该更改的属性，并允许服务器为客户机未指定的任何属性保留现有数据。

> 使用 PATCH 的方法不止一种
>
> PATCH 方式应用于 patchOrder() 方法时，有两个限制：
>
> * 如果传递的是 null 值，意味着没有变化，那么客户端如何指示字段应该设置为 null？
> * 没有办法从一个集合中移除或添加一个子集。如果客户端想要从集合中添加或删除一条数据，它必须发送完整的修改后的集合。
>
> 对于应该如何处理 PATCH 请求或传入的数据应该是什么样子，确实没有硬性规定。客户端可以发送应用于特定 PATCH 请求的描述，这个描述包含着需要被应用于数据的更改，而不是发送实际的域数据。当然，必须编写请求处理程序来处理 PATCH 指令，而不是域数据。

在 @PutMapping 和 @PatchMapping 中，请注意请求路径引用了将要更改的资源。这与 @GetMappingannotated 方法处理路径的方式相同。

现在已经了解了如何使用 @GetMapping 和 @PostMapping 来获取和发布资源。已经看到了使用 @PutMapping 和 @PatchMapping 更新资源的两种不同方法，剩下的工作就是处理删除资源的请求。
