使用 REST 访问 JPA 数据

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

您将构建什么

您将使用 Spring Data REST 构建一个 Spring 应用程序,该应用程序允许您创建和检索存储在数据库中的 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 开始

您可以使用这个预配置项目,然后点击 Generate 下载 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 中的 main 方法来运行应用程序。您可以在 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/people 处有一个 people 链接。它有一些选项,例如 ?page?size?sort

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

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

$ 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 的后端的应用程序。

获取代码