Выбрать главу

Если PUT выполняет оптовую замену данных объекта, то как вам следует обрабатывать запросы, чтобы выполнить только частичное обновление? Вот для чего хороши запросы HTTP PATCH и Spring @PatchMapping. Вот как вы можете написать метод контроллера для обработки запроса PATCH для заказа:

@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, включая @Pathmapping и @PutMapping, указывают только, какие типы запросов должен обрабатывать метод. Эти аннотации не определяют, как будет обрабатываться запрос. Даже если PATCH семантически подразумевает частичное обновление, вы должны написать код в методе-обработчике, который фактически выполняет такое обновление.

В случае метода putOrder() вы приняли полные данные для заказа и сохранили их, придерживаясь семантики HTTP PUT. Но для того, чтобы patchMapping() придерживался семантики HTTP PATCH, тело метода требует большего интеллекта. Вместо полной замены заказа новыми отправленными данными, он проверяет каждое поле входящего объекта заказа и применяет любые не-null значения к существующему заказу. Этот подход позволяет клиенту отправлять только те свойства, которые должны быть изменены, и позволяет серверу сохранять существующие данные для любых свойств, не указанных клиентом.

Существует более одного подхода к реализации PATCH

Подход исправления, применяемый в методе patchOrder(), имеет несколько ограничений:

Если null значения предназначены для указания отсутствия изменений, как клиент может указать, что поле должно быть установлено в null?

Невозможно удалить или добавить подмножество элементов из коллекции. Если клиент хочет добавить или удалить запись из коллекции, он должен отправить полную измененную коллекцию.

На самом деле не существует строгого правила о том, как обрабатывать запросы PATCH или как должны выглядеть входящие данные. Вместо того, чтобы отправлять фактические данные домена, клиент может отправить специфическое для патча описание изменений, которые будут применены. Конечно, обработчик запроса должен был бы быть написан для обработки инструкций патча вместо данных домена.

Обратите внимание, что и в @PutMapping, и в @PatchMapping путь запроса ссылается на ресурс, который необходимо изменить. Это те же самые пути что обрабатываются с помощью методов аннотированных @GetMapping.

Теперь вы видели, как получать и обновлять ресурсы с помощью @GetMapping и @PostMapping. И вы видели два разных способа обновления ресурса с помощью @PutMapping и @PatchMapping. Осталось только обработать запросы на удаление ресурса.

6.1.4 Удаление данных с сервера

Иногда данные просто больше не нужны. В этих случаях клиент должен иметь возможность запросить удаление ресурса с помощью HTTP DELETE request.

Spring MVC @DeleteMapping пригодится для объявления методов, которые обрабатывают запросы на удаление. Например, предположим, вы хотите, чтобы ваш API разрешал удаление ресурса заказа. Следующий метод контроллера должен реализовать этот функционал:

@DeleteMapping("/{orderId}")

@ResponseStatus(code=HttpStatus.NO_CONTENT)

public void deleteOrder(@PathVariable("orderId") Long orderId) {

 try {

    repo.deleteById(orderId);

 } catch (EmptyResultDataAccessException e) {}

}

К этому моменту принцип построения аннотаций должна быть вам знакома. Вы уже видели @GetMapping, @PostMapping, @PutMapping и @ PatchMapping, каждый из которых указывает, что метод должен обрабатывать HTTP запросы соответствующие им методов. Возможно, вас не удивит, что @DeleteMapping используется для указания того, что метод deleteOrder() отвечает за обработку запросов DELETE для /orders/{orderId}.

Код в методе - это то, что фактически выполняет удаление заказа. В этом случае он принимает идентификатор заказа, предоставленный в качестве переменной пути в URL-адресе, и передает его в метод deleteById () репозитория. Если заказ существует при вызове этого метода, он будет удален. Если заказ не существует, будет выброшено исключение EmptyResultDataAccessException.