Spring Boot 与 Docker
观察 GraphQL 的实际运行

本指南将引导您创建一个应用程序,该应用程序通过基于超媒体的 RESTful 前端访问关系型 JPA 数据。

你将构建什么

您将构建一个 Spring 应用程序,该应用程序允许您使用 Spring Data REST 创建和检索存储在数据库中的 Person 对象。Spring Data REST 结合了 Spring HATEOASSpring Data JPA 的特性,并自动将它们集成在一起。

Spring Data REST 还支持将 Spring Data Neo4jSpring Data GemfireSpring Data MongoDB 作为后端数据存储,但本指南不涵盖这些内容。

所需条件

  • 大约 15 分钟

  • 一个喜欢的文本编辑器或 IDE

  • Java 17 或更高版本

如何完成本指南

与大多数 Spring 入门指南一样,您可以从头开始并完成每个步骤,或者通过查看此仓库中的代码直接跳转到解决方案。

要在本地环境中查看最终结果,您可以执行以下操作之一:

从 Spring Initializr 开始

您可以使用这个预初始化项目,点击生成以下载 ZIP 文件。该项目已配置为适合本教程中的示例。

要手动初始化项目:

  1. 访问 https://start.spring.io。该服务会拉取应用程序所需的所有依赖项,并为您完成大部分设置工作。

  2. 选择 Gradle 或 Maven 以及您要使用的编程语言。本指南假设您选择了 Java。

  3. 点击 Dependencies,然后选择 Rest RepositoriesSpring Data JPAH2 Database

  4. 点击 Generate

  5. 下载生成的 ZIP 文件,该文件是根据您的选择配置好的 Web 应用程序的归档包。

如果您的 IDE 集成了 Spring Initializr,您可以直接在 IDE 中完成这个过程。

您也可以从 Github 上 fork 项目,并在您的 IDE 或其他编辑器中打开它。

创建一个领域对象

创建一个新的领域对象来表示一个人,如下面的代码清单所示(位于 src/main/java/com/example/accessingdatarest/Person.java 中):

package com.example.accessingdatarest;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Person {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  private String firstName;
  private String lastName;

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
}

Person对象包含名字和姓氏。(还有一个配置为自动生成的ID对象,因此您无需处理它。)

创建 Person 仓库

接下来,您需要创建一个简单的仓库,如下面的清单(位于 src/main/java/com/example/accessingdatarest/PersonRepository.java 中)所示:

package com.example.accessingdatarest;

import java.util.List;

import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long>, CrudRepository<Person,Long> {

  List<Person> findByLastName(@Param("name") String name);

}

该仓库是一个接口,允许您执行涉及 Person 对象的各种操作。它通过扩展 Spring Data Commons 中定义的 PagingAndSortingRepository 接口来获取这些操作。

在运行时,Spring Data REST 会自动创建该接口的实现。然后,它使用 @RepositoryRestResource 注解来引导 Spring MVC 在 /people 路径下创建 RESTful 端点。

@RepositoryRestResource 并不是导出仓库所必需的。它仅用于更改导出的细节,例如使用 /people 而不是默认的 /persons

在这里,您还定义了一个自定义查询,用于根据 lastName 检索 Person 对象的列表。您可以在本指南的后续部分中看到如何调用它。

Spring Boot 会自动启动 Spring Data JPA,以创建 PersonRepository 的具体实现,并通过 JPA 配置其与后端内存数据库进行通信。

Spring Data REST 构建在 Spring MVC 之上。它创建了一系列 Spring MVC 控制器、JSON 转换器和其他 bean,以提供一个 RESTful 的前端。这些组件与 Spring Data JPA 后端连接。当您使用 Spring Boot 时,所有这些都会自动配置。如果您想深入了解其工作原理,可以查看 Spring Data REST 中的 RepositoryRestMvcConfiguration

运行应用程序

现在,您可以通过执行AccessingDataRestApplication中的主方法来运行应用程序。您可以从IDE中运行程序,或者在项目根目录下执行以下Gradle命令:

./gradlew bootRun

或者,您可以使用 Maven 通过以下命令来运行应用程序:

./mvnw spring-boot:run

测试应用程序

既然应用程序已经运行,您可以对其进行测试。您可以使用任何您喜欢的 REST 客户端。以下示例使用了 *nix 工具 curl

首先,您想查看顶层服务。以下示例展示了如何操作:

$ curl http://localhost:8080
{
  "_links" : {
    "people" : {
      "href" : "http://localhost:8080/people{?page,size,sort}",
      "templated" : true
    }
  }
}

前面的示例提供了该服务器功能的第一印象。位于http://localhost:8080/peoplepeople链接提供了一些选项,例如?page?size?sort

Spring Data REST 使用 HAL 格式 作为 JSON 输出。它非常灵活,并提供了一种便捷的方式来在提供的数据旁边附加链接。

下面的示例展示了如何查看人员记录(目前没有记录):

$ curl http://localhost:8080/people
{
  "_embedded" : {
    "people" : []
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people{?page,size,sort}",
      "templated" : true
    },
    "search" : {
      "href" : "http://localhost:8080/people/search"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 0,
    "totalPages" : 0,
    "number" : 0
  }
}

目前没有任何元素,因此也没有任何页面。是时候创建一个新的 Person 了!以下列表展示了如何做到这一点:

$ curl -i -H "Content-Type:application/json" -d '{"firstName": "Frodo", "lastName": "Baggins"}' http://localhost:8080/people
HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Location: http://localhost:8080/people/1
Content-Length: 0
Date: Wed, 26 Feb 2014 20:26:55 GMT
  • -i: 确保您可以看到响应消息,包括头部信息。新创建的 Person 的 URI 会显示出来。

  • -H "Content-Type:application/json": 设置内容类型,以便应用程序知道负载包含一个 JSON 对象。

  • -d '{"firstName": "Frodo", "lastName": "Baggins"}': 这是正在发送的数据。

  • 如果您使用的是 Windows,上述命令可以在 WSL 上运行。如果您无法安装 WSL,可能需要将单引号替换为双引号,并转义现有的双引号,即 -d "{\"firstName\": \"Frodo\", \"lastName\": \"Baggins\"}"

请注意,POST 操作的响应中包含一个 Location 头。该头包含了新创建资源的 URI。Spring Data REST 还提供了两个方法(RepositoryRestConfiguration.setReturnBodyOnCreate(…)setReturnBodyOnUpdate(…)),您可以使用这些方法来配置框架,使其立即返回刚刚创建的资源的表示形式。RepositoryRestConfiguration.setReturnBodyForPutAndPost(…) 是一个快捷方法,用于为创建和更新操作启用表示形式的响应。

您可以查询所有人的信息,如下例所示:

$ curl http://localhost:8080/people
{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people{?page,size,sort}",
      "templated" : true
    },
    "search" : {
      "href" : "http://localhost:8080/people/search"
    }
  },
  "_embedded" : {
    "people" : [ {
      "firstName" : "Frodo",
      "lastName" : "Baggins",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/people/1"
        }
      }
    } ]
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

people 对象包含了一个包含 Frodo 的列表。请注意它如何包含一个 self 链接。Spring Data REST 还使用了 Evo Inflector 来对实体的名称进行复数化以便分组。

你可以直接查询单个记录,如下所示:

$ curl http://localhost:8080/people/1
{
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/1"
    }
  }
}

这看起来可能纯粹是基于 web 的。然而,幕后其实有一个 H2 关系型数据库在生产环境中,您可能会使用一个真实的数据库,比如 PostgreSQL。

在本指南中,只有一个领域对象。在更复杂的系统中,领域对象之间相互关联,Spring Data REST 会渲染额外的链接,以帮助导航到相关的记录。

您可以找到所有自定义查询,如下例所示:

$ curl http://localhost:8080/people/search
{
  "_links" : {
    "findByLastName" : {
      "href" : "http://localhost:8080/people/search/findByLastName{?name}",
      "templated" : true
    }
  }
}

您可以看到查询的URL,包括HTTP查询参数name。请注意,这与嵌入在接口中的@Param("name")注解相匹配。

以下示例展示了如何使用findByLastName查询:

$ curl http://localhost:8080/people/search/findByLastName?name=Baggins
{
  "_embedded" : {
    "persons" : [ {
      "firstName" : "Frodo",
      "lastName" : "Baggins",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/people/1"
        }
      }
    } ]
  }
}

因为您在代码中定义了它返回 List<Person>,所以它会返回所有结果。如果您将其定义为仅返回 Person,它会选择其中一个 Person 对象返回。由于这种行为可能不可预测,您可能不希望在对可能返回多个条目的查询中这样做。

您还可以发出 PUTPATCHDELETE REST 调用来分别替换、更新或删除现有记录。以下示例使用 PUT 调用:

$ curl -X PUT -H "Content-Type:application/json" -d '{"firstName": "Bilbo", "lastName": "Baggins"}' http://localhost:8080/people/1
$ curl http://localhost:8080/people/1
{
  "firstName" : "Bilbo",
  "lastName" : "Baggins",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/1"
    }
  }
}

以下示例使用了 PATCH 调用:

$ curl -X PATCH -H "Content-Type:application/json" -d '{"firstName": "Bilbo Jr."}' http://localhost:8080/people/1
$ curl http://localhost:8080/people/1
{
  "firstName" : "Bilbo Jr.",
  "lastName" : "Baggins",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/1"
    }
  }
}

PUT 会替换整个记录。未提供的字段将被替换为 null。您可以使用 PATCH 来更新部分项。

您也可以删除记录,如下例所示:

$ curl -X DELETE http://localhost:8080/people/1
$ curl http://localhost:8080/people
{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people{?page,size,sort}",
      "templated" : true
    },
    "search" : {
      "href" : "http://localhost:8080/people/search"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 0,
    "totalPages" : 0,
    "number" : 0
  }
}

这种基于超媒体的接口的一个便利之处在于,您可以使用 curl(或任何您喜欢的 REST 客户端)来发现所有的 RESTful 端点。您无需与客户交换正式的合同或接口文档。

总结

恭喜!您已经开发了一个基于超媒体的 RESTful 前端和基于 JPA 的后端应用程序。

另请参阅

以下指南可能也会有所帮助:

本页目录