Я уже переработал клиентский код для формы дизайна taco, создав новый Angular компонент с именем DesignComponent (в файле с именем design.component.ts). Поскольку в нем должен присутствовать функционал отправки данных формы, DesignComponent имеет метод onSubmit (), который выглядит следующим образом:
onSubmit() {
this.httpClient.post(
'http://localhost:8080/design',
this.model, {
headers: new HttpHeaders().set('Content-type', 'application/json'),
}).subscribe(taco => this.cart.addToCart(taco));
this.router.navigate(['/cart']);
}
В методе onSubmit() вместо get() вызывается метод post(). Это означает, что вместо извлечения данных из API, вы отправляете данные в API. В частности, вы отправляете дизайн тако, который хранится в переменной model, в API endpoint /design с request-ом HTTP POST.
Это означает, что вам нужно будет написать метод в DesignTacoController для обработки этого запроса и сохранения дизайна. Добавив следующий метод postTaco() в DesignTacoController, вы реализуете функционал в контроллере для этой цели:
@PostMapping(consumes="application/json")
@ResponseStatus(HttpStatus.CREATED)
public Taco postTaco(@RequestBody Taco taco) {
return tacoRepo.save(taco);
}
Поскольку postTaco() будет обрабатывать HTTP POST request , он аннотируется @PostMapping вместо @GetMapping. Вы не указываете здесь атрибут path, поэтому метод postTaco() будет обрабатывать запросы для /design, как указано в классе @RequestMapping у DesignTacoController.
Вы устанавливаете атрибут consumes. Здесь вы используете consumes, чтобы сказать, что метод будет обрабатывать только запросы, Content-type которых соответствует application/json.
Параметр Taco для метода помечается @RequestBody, чтобы указать, что тело запроса должно быть преобразовано в объект Taco и привязано к параметру. Эта аннотация важна, без нее Spring MVC предполагает, что вы хотите, чтобы параметры запроса (либо параметры запроса, либо параметры формы) были связаны с объектом Taco. Но аннотация @RequestBody гарантирует, что вместо этого JSON в теле запроса будет связан с объектом Taco.
Как только postTaco() получает объект Taco, он передает его методу save() в TacoRepository.
Возможно, вы также заметили, что я аннотировал метод postTaco() с помощью @ResponseStatus (HttpStatus.CREATED). При нормальных обстоятельствах (когда не генерируются исключения) все ответы будут иметь код состояния HTTP 200 (ОК), что указывает на успешность запроса. Хотя ответ HTTP 200 всегда приветствуется, он не всегда достаточно описательный. В случае запроса POST HTTP-статус 201 (CREATED) является более информативным. Он сообщает клиенту, что запрос был не только успешным, но в результате был создан ресурс. Всегда целесообразно использовать @ResponseStatus, где это уместно, для передачи клиенту наиболее описательного и точного кода состояния HTTP.
Хотя вы использовали @PostMapping для создания нового ресурса Taco, POST-запросы также можно использовать для обновления ресурсов. Тем не менее, запросы POST обычно используются для создания ресурсов, а запросы PUT и PATCH используются для обновления ресурсов. Давайте посмотрим, как вы можете обновить данные, используя @PutMapping и @PatchMapping.
6.1.3 Обновление данных на сервере
Прежде чем написать какой-либо код контроллера для обработки команд HTTP PUT или PATCH, вам следует уделить время рассмотрению слона в комнате: почему существуют два разных метода HTTP для обновления ресурсов?
Хотя это правда, что PUT часто используется для обновления данных ресурсов, на самом деле это семантическая противоположность GET. В то время как запросы GET предназначены для передачи данных с сервера на клиент, запросы PUT предназначены для отправки данных с клиента на сервер.
В этом смысле PUT действительно предназначен для выполнения операции оптовой замены, а не операции обновления. Напротив, целью HTTP PATCH является выполнение исправления или частичное обновление данных ресурса.
Например, предположим, что вы хотите изменить адрес заказа. Один из способов добиться этого с помощью REST API - обработать запрос PUT следующим образом:
@PutMapping("/{orderId}")
public Order putOrder(@RequestBody Order order) {
return repo.save(order);
}
Это может сработать, но для этого потребуется, чтобы клиент передал полные данные заказа в запросе PUT. Семантически PUT означает «передать эти данные по этому URL», по сути, заменяя любые данные, которые уже есть. Если какое-либо из свойств заказа будет опущено, значение этого свойства будет заменено на ноль. Даже тако в заказе необходимо будет передать вместе с данными заказа, иначе они будут удалены из заказа.
Если 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.