vlambda博客
学习文章列表

读书笔记《building-a-restful-web-service-with-spring》REST中的CRUD操作

Chapter 5. CRUD Operations in REST

第3章第一个端点中,我们创建了我们的第一个 RESTful 端点来访问我们示例物业管理系统中的房间。检索数据的请求通常映射到 RESTful Web 服务中的 HTTP GET 方法。

现在,我们将扩展前面的示例并实现剩余的端点以支持所有 CRUD(创建、读取、更新和删除)操作。

在本章中,我们将介绍以下主题:

  • 将 CRUD 操作映射到 HTTP 方法

  • 创建资源

  • 更新资源

  • 删除资源

  • 测试 RESTful 操作

  • 模拟 PUTDELETE 方法

Mapping CRUD operations to HTTP methods


HTTP 1.1 规范 定义了以下 方法:

  • OPTIONS:此方法表示对所请求的 URI。这通常不直接 与 REST 一起使用。但是,此方法可以用作底层 通信的一部分。例如,在从网页消费 Web 服务时可以使用此方法(作为跨域资源共享机制的一部分)。

  • GET:该方法检索 请求的 URI。在 RESTful Web 服务的上下文中,此方法用于检索资源。如第3章所示,第一个端点 ,这是用于 读取操作(CRUD 中的 R)的方法。

  • HEAD:这些请求在语义上与 GET 请求相同,只是响应的主体是 未传输。此方法对于获取有关资源的元信息很有用。类似于 OPTIONS 方法,这种方法通常不直接用于 REST Web 服务中。

  • POST:该方法用于 指示服务器接受请求中包含的实体作为新的资源。创建操作通常 映射到此 HTTP 方法。

  • PUT:此方法请求服务器将包含的实体存储在请求URI下。为了支持更新 REST 资源,可以利用此方法。根据 HTTP 规范,如果实体不存在,服务器可以创建资源。由 Web 服务设计者决定 是否应实现此行为,或者是否应仅由 POST 请求。

  • DELETE:最后一个未映射的操作是删除资源。 HTTP 规范定义了一个 DELETE 方法,该方法在语义上与 RESTful 资源的删除一致

  • TRACE:此方法用于 在 Web 服务器上执行操作。这些操作通常旨在帮助 HTTP 应用程序的开发和测试。 TRACE 请求通常不会映射到任何 特定的 RESTful 操作。

  • CONNECT:此 HTTP 方法定义为通过 一个代理服务器。由于它处理传输层问题,因此该方法没有到 RESTful 操作的自然语义映射。

RESTful 架构不要求使用 HTTP 作为通信协议。此外,即使选择 HTTP 作为底层传输,也没有规定将 RESTful 操作映射到 HTTP 方法。开发人员可以通过 POST 请求切实支持所有操作。

话虽如此,以下 CRUD 到 HTTP 方法的映射通常用于 REST Web 服务:

手术

HTTP 方法

创造

POST

GET

更新

PUT

删除

删除

我们的示例 Web 服务将使用这些 HTTP 方法来支持 CRUD 操作。本章的其余部分将说明如何 构建此类操作。

Creating resources


我们示例物业管理系统的inventory 组件处理房间。在第3章第一个端点中,我们建立一个端点来访问房间。让我们看看如何定义一个端点来创建新资源:

@RestController
@RequestMapping("/rooms")
public class RoomsResource {

  @RequestMapping(method = RequestMethod.POST)
  public ApiResponse addRoom(@RequestBody RoomDTO room) {
    Room newRoom = createRoom(room);
    return new ApiResponse(Status.OK, new RoomDTO(newRoom));
  }
}

我们在 RoomsResource 类中添加了一个新方法来处理新房间的创建。如第3章所述,第一个端点 , @RequestMapping 用于将请求映射到 Java 方法。在这里,我们将 POST 请求映射到 addRoom()

Tip

@RequestMapping 中不指定值(路径)等同于使用“/”。

我们将新房间作为 @RequestBody 注释传递。此注解指示 Spring 将传入 Web 请求的主体映射到方法参数。此处使用 Jackson 将 JSON 请求正文转换为 Java 对象。

使用这种新方法,使用以下 JSON 正文向 http://localhost:8080/rooms 发送请求将导致创建一个新房间:

{
  name: "Cool Room",
  description: "A room that is very cool indeed", room_category_id: 1
}

我们的新方法将返回新创建的房间:

{
  "status":"OK",
  "data":{
    "id":2,
    "name":"Cool Room",
    "room_category_id":1,
    "description":"A room that is very cool indeed"
  }
}

我们可以决定只返回新资源的 ID 以响应资源创建。但是,由于我们可能会清理或以其他方式处理发送过来的数据,因此最好 返回完整资源。

Quickly testing endpoints

第 8 章测试 RESTful Web 服务,将详细介绍了如何有效地测试 RESTful Web 服务,但是为了快速测试我们新创建的端点的 目的,让我们看看如何测试使用 Postman 创建新房间。

Note

Postman (https://www.getpostman.com) 是一个 Google Chrome 插件扩展,它提供用于构建和测试 Web API 的工具。

以下屏幕截图说明了如何使用 Postman 测试此端点:

读书笔记《building-a-restful-web-service-with-spring》REST中的CRUD操作

在 Postman 中,我们 指定我们将 POST 请求发送到 URL (http://localhost:8080/rooms) 带有内容类型标头 (application/json) 和请求正文。如下所示发送此请求将导致创建并返回一个新房间:

读书笔记《building-a-restful-web-service-with-spring》REST中的CRUD操作

我们已经使用 Postman 成功地为我们的库存服务添加了一个房间。创建不完整的请求同样容易,以确保我们的端点在将数据持久化到数据库之前执行任何必要的健全性检查。

第 8 章测试 RESTful Web 服务,将讨论测试 RESTful Web 服务的其他方法。

JSON versus form data

发布表单是在 Web 上创建新实体的传统方式,它可以很容易地用于 创建新的 RESTful 资源。我们可以将方法更改为以下内容:

@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ApiResponse addRoom(String name, String description, long roomCategoryId) {
  Room room = createRoom(name, description, roomCategoryId);
  return new ApiResponse(Status.OK, new RoomDTO(room));
}

与前面方法的主要区别在于,我们告诉 Spring 映射表单请求(即,使用内容类型,application/x-www-form-urlencoded)而不是 JSON要求。此外,我们不期望将对象作为参数,而是单独接收每个字段。

Tip

默认情况下,Spring 将使用 Java 方法属性名称来映射传入的表单输入。开发人员可以通过使用 @RequestParam("…") 注释属性来指定输入名称来更改此行为。

在主要 Web 服务消费者是 Web 应用程序的情况下,使用表单请求可能更适用。然而,在大多数情况下,前一种方法更符合 RESTful 原则,应该受到青睐。此外,当处理复杂的资源时,表单请求将被证明使用起来很麻烦。从开发人员的角度来看,将对象映射委托给第三方库(例如 Jackson)更容易。

现在我们已经创建了一个新资源,让我们看看如何更新它。

Updating resources


选择 URI 格式是 设计 RESTful API 的重要部分。如前所述,使用路径 /rooms/{roomId} 访问房间,并在 /rooms 下创建房间。您可能还记得,根据 HTTP 规范,PUT 请求可能会导致创建实体(如果它们不存在)。根据更新请求创建新资源的决定取决于服务设计者。但是,它确实会影响用于此类请求的路径选择。

从语义上讲,PUT 请求更新存储在提供的请求 URI 下的实体。这意味着更新请求应该使用与 GET 请求相同的 URI:/rooms/{roomId}。然而,这种方法阻碍了在更新时支持资源创建的能力,因为没有可用的房间标识符。

我们可以使用的替代路径是 /rooms,房间标识符在请求正文中传递。使用这种方法,当资源不包含PUT 请求可以被视为 POST 请求="indexterm"> 标识符。

鉴于第一种方法在语义上更准确,我们将选择不支持更新时创建资源,我们将使用以下路径来处理 PUT 请求: /rooms/{roomId}。

The update endpoint

以下方法 提供了修改房间所需的端点:

@RequestMapping(value = "/{roomId}", method = RequestMethod.PUT)
public ApiResponse updateRoom(@PathVariable long roomId, @RequestBody RoomDTO updatedRoom) {
  try {
    Room room = updateRoom(updatedRoom);
    return new ApiResponse(Status.OK, new RoomDTO(room));
  } catch (RecordNotFoundException e) {
    return new ApiResponse(Status.ERROR, null, new ApiError(999, "No room with ID " + roomId));
  }
}

正如本章开头所讨论的,我们将更新请求映射到 HTTP PUT 动词。用 @RequestMapping(value = "/{roomId}", method = RequestMethod.PUT) 注释这个方法指示 Spring 直接 PUT 在这里请求。

房间标识符是路径的一部分并映射到第一个方法参数。与资源创建请求类似,我们使用 @RequestBody 将主体映射到第二个参数。

Testing update requests

使用 Postman,我们可以快速创建一个测试用例来更新我们之前创建的房间。为此,我们发送一个带有以下正文的 PUT 请求:

{
  id: 2,
  name: "Cool Room",
  description: "A room that is really very cool indeed",
    room_category_id: 1
}

结果响应将是更新后的房间:

{
  "status": "OK",
  "data": {
    "id": 2,
    "name": "Cool Room",
    "room_category_id": 1,
    "description": "A room that is really very cool indeed."
  }
}

如果我们尝试更新一个不存在的房间,服务器将生成以下响应:

{
  "status": "ERROR",
  "error": {
    "error_code": 999,
    "description": "No room with ID 3"
  }
}

由于我们不支持在更新时创建资源,因此服务器会返回一个错误,指示找不到资源。

Note

请参阅 第 4 章数据表示,了解关于错误处理和响应格式的讨论。

Deleting resources


毫不奇怪,我们使用 DELETE 动词来删除 REST 资源。此外,您已经知道删除请求的路径是 /rooms/{roomId}

处理房间删除的Java方法如下所示:

@RequestMapping(value = "/{roomId}", method = RequestMethod.DELETE)
public ApiResponse deleteRoom(@PathVariable long roomId) {
  try {
    Room room = inventoryService.getRoom(roomId);
    inventoryService.deleteRoom(room.getId());
    return new ApiResponse(Status.OK, null);
  } catch (RecordNotFoundException e) {
    return new ApiResponse(Status.ERROR, null, new ApiError(999, "No room with ID " + roomId));
  }
}

通过将请求映射方法声明为 RequestMethod.DELETE,Spring 将使该方法处理 DELETE 请求。

由于资源被删除,在响应中返回它没有多大意义。服务设计者可以选择返回一个布尔标志来指示资源已成功删除。在我们的例子中,我们利用响应的状态元素将该信息传递给消费者(参见 Chapter 4数据表示,了解有关响应格式的更多详细信息)。删除房间的响应如下:

{
  "status": "OK"
}

通过这个操作,我们现在有一个完整的 CRUD API 用于我们的库存服务。在结束本章之前,让我们讨论一下 REST 开发人员如何处理并非所有 HTTP 动词都可以使用的情况。

Overriding the HTTP method


在某些情况下(例如,当服务或其消费者位于过度的 企业防火墙之后,或者如果主要消费者是网页),只有GETPOST HTTP 方法可能可用。在这种情况下,可以通过在请求中传递自定义标头来模拟丢失的动词。

例如,可以使用 POST 请求通过设置自定义标头来处理资源更新(例如,X-HTTP-Method-Override) 到 PUT 表示我们正在通过 POSTPUT 请求> 请求。以下方法将处理这种情况:

@RequestMapping(value = "/{roomId}", method = RequestMethod.POST, headers = {"X-HTTP-Method-Override=PUT"})
public ApiResponse updateRoomAsPost(@PathVariable("roomId") long id, @RequestBody RoomDTO updatedRoom) {
  return updateRoom(id, updatedRoom);
}

通过在映射注解上设置headers属性,Spring请求路由会拦截POST 使用我们的自定义标头请求并调用此方法。普通的 POST 请求仍将映射到我们为创建新房间而组合在一起的 Java 方法。

Summary


在本章中,我们通过添加管理房间资源所需的所有 CRUD 操作来继续实现示例 RESTful Web 服务。我们讨论了如何组织 URI 以最好地体现 REST 原则,还研究了如何使用 Postman 快速测试端点。既然我们的系统组件功能齐全,我们可以花一些时间来讨论性能。

在下一章中,我们将了解如何管理 RESTful 端点的性能、可以采用哪些技术以及需要注意的事项。