使用 REST 访问 Pivotal GemFire 中的数据

本指南将引导您创建一个应用,该应用通过一个基于超媒体的 REST 前端访问存储在 Apache Geode 中的数据。

您将构建什么

您将构建一个 Spring Web 应用,该应用允许您使用 Spring Data REST 创建和检索存储在 Apache Geode 内存数据网格 (IMDG) 中的 Person 对象。Spring Data REST 自动集成了 Spring HATEOASSpring Data for Apache Geode 的特性。

Spring Data REST 还支持 Spring Data JPASpring Data MongoDBSpring Data Neo4j 作为后端数据存储,但这些不属于本指南的范围。
要了解有关 Apache Geode 概念以及如何从 Apache Geode 访问数据的更多通用知识,请阅读指南:使用 Apache Geode 访问数据

您将需要什么

如何完成本指南

与大多数 Spring 入门指南一样,您可以从头开始并完成每个步骤,或者您可以跳过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到可以运行的代码。

从头开始,请继续阅读从 Spring Initializr 开始

跳过基础部分,请执行以下操作

完成后,您可以对照 gs-accessing-gemfire-data-rest/complete 中的代码检查您的结果。

从 Spring Initializr 开始

对于所有的 Spring 应用,您都应该从 Spring Initializr 开始。Spring Initializr 提供了一种快速获取应用所需所有依赖并进行大量设置的方式。本示例需要 "Spring for Apache Geode" 依赖。

以下列表显示了使用 Maven 时的示例 pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.0</version>
	</parent>

	<groupId>org.springframework</groupId>
	<artifactId>gs-accessing-gemfire-data-rest</artifactId>
	<version>0.1.0</version>

	<properties>
		<spring-shell.version>1.2.0.RELEASE</spring-shell.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-rest</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-geode</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.shell</groupId>
			<artifactId>spring-shell</artifactId>
			<version>${spring-shell.version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

以下列表显示了使用 Gradle 时的示例 build.gradle 文件

plugins {
    id 'org.springframework.boot' version '2.7.0'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'io.freefair.lombok' version '6.3.0'
    id 'java'
}

apply plugin: 'eclipse'
apply plugin: 'idea'

group = "org.springframework"
version = "0.1.0"
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {

    implementation "org.springframework.boot:spring-boot-starter-data-rest"
    implementation "org.springframework.data:spring-data-geode"
    implementation "org.projectlombok:lombok"

    runtimeOnly "org.springframework.shell:spring-shell:1.2.0.RELEASE"

    testImplementation "org.springframework.boot:spring-boot-starter-test"

}

test {
    useJUnitPlatform()
}

bootJar {
    baseName = 'gs-accessing-gemfire-data-rest'
    version =  '0.1.0'
}

创建域对象

创建一个新的域对象来表示一个人。

src/main/java/hello/Person.java

package hello;

import java.util.concurrent.atomic.AtomicLong;

import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.gemfire.mapping.annotation.Region;

import lombok.Data;

@Data
@Region("People")
public class Person {

  private static AtomicLong COUNTER = new AtomicLong(0L);

  @Id
  private Long id;

  private String firstName;
  private String lastName;

  @PersistenceConstructor
  public Person() {
    this.id = COUNTER.incrementAndGet();
  }
}

Person 对象有名字和姓氏。Apache Geode 域对象需要一个 ID,因此使用了 AtomicLong 来在每个 Person 对象创建时递增。

创建 Person 仓库

接下来,您需要创建一个简单的仓库 (Repository) 来持久化/访问存储在 Apache Geode 中的 Person 对象。

src/main/java/hello/PersonRepository.java

package hello;

import java.util.List;

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 CrudRepository<Person, Long> {

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

}

这个仓库 (Repository) 是一个接口,它允许您执行涉及 Person 对象的各种数据访问操作(例如基本的 CRUD 和简单查询)。它通过扩展 CrudRepository 获得这些操作。

在运行时,Spring Data for Apache Geode 将自动创建此接口的实现。然后,Spring Data REST 将使用 @RepositoryRestResource 注解,指示 Spring MVC 在 /people 处创建 REST 端点。

导出仓库 (Repository) 不需要使用 @RepositoryRestResource。它仅用于更改导出详情,例如使用 /people 代替默认值 /persons

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

使应用可执行

虽然可以将此服务打包成传统的 WAR 文件部署到外部应用服务器,但下面演示的更简单的方法是创建一个独立应用。您将所有内容打包到一个单独的可执行 JAR 文件中,由经典的 Java main() 方法驱动。在此过程中,您使用了 Spring 对嵌入 Tomcat servlet 容器作为 HTTP 运行时的支持,而不是部署到外部 servlet 容器。

src/main/java/hello/Application.java

package hello;

import org.apache.geode.cache.client.ClientRegionShortcut;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories;

@SpringBootApplication
@ClientCacheApplication(name = "AccessingGemFireDataRestApplication")
@EnableEntityDefinedRegions(
  basePackageClasses = Person.class,
  clientRegionShortcut = ClientRegionShortcut.LOCAL
)
@EnableGemfireRepositories
@SuppressWarnings("unused")
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

@SpringBootApplication 是一个便捷注解,它添加了以下所有功能

  • @Configuration:将类标记为应用上下文的 Bean 定义源。

  • @EnableAutoConfiguration:告诉 Spring Boot 根据类路径设置、其他 Bean 和各种属性设置开始添加 Bean。例如,如果类路径中有 spring-webmvc,此注解会将应用标记为 Web 应用并激活关键行为,例如设置 DispatcherServlet

  • @ComponentScan:告诉 Spring 在 hello 包中查找其他组件、配置和服务,从而找到控制器。

main() 方法使用 Spring Boot 的 SpringApplication.run() 方法启动应用。您是否注意到没有一行 XML 代码?也没有 web.xml 文件。这个 Web 应用是 100% 纯 Java 的,您无需处理任何底层或基础设施的配置。

@EnableGemfireRepositories 注解激活 Spring Data for Apache Geode仓库 (Repositories) 功能。Spring Data for Apache Geode 将创建 PersonRepository 接口的具体实现,并将其配置为与嵌入式的 Apache Geode 实例通信。

构建可执行 JAR

您可以使用 Gradle 或 Maven 从命令行运行应用。您还可以构建一个包含所有必要依赖、类和资源的单个可执行 JAR 文件并运行它。构建可执行 JAR 使服务在整个开发生命周期中、跨不同环境等情况下,易于交付、版本化和部署为应用。

如果您使用 Gradle,您可以使用 ./gradlew bootRun 运行应用。或者,您可以使用 ./gradlew build 构建 JAR 文件,然后按如下方式运行该 JAR 文件

java -jar build/libs/gs-accessing-gemfire-data-rest-0.1.0.jar

如果您使用 Maven,您可以使用 ./mvnw spring-boot:run 运行应用。或者,您可以使用 ./mvnw clean package 构建 JAR 文件,然后按如下方式运行该 JAR 文件

java -jar target/gs-accessing-gemfire-data-rest-0.1.0.jar
此处描述的步骤会创建一个可运行的 JAR。您还可以构建一个经典的 WAR 文件

将显示日志输出。服务应在几秒钟内启动并运行。

测试应用

应用运行后,您可以测试它。您可以使用任何您喜欢的 REST 客户端。以下示例使用了 *nix 工具 curl

首先您想查看顶层服务。

$ curl http://localhost:8080
{
  "_links" : {
    "people" : {
      "href" : "http://localhost:8080/people"
    }
  }
}

在这里,您将第一次看到这个服务器提供了什么。有一个名为 people 的链接位于 http://localhost:8080/peopleSpring Data for Apache Geode 不像其他 Spring Data REST 指南那样支持分页,因此没有额外的导航链接。

Spring Data REST 使用 HAL 格式输出 JSON。它非常灵活,并提供了一种方便的方式在所提供的数据旁边提供链接。
$ curl http://localhost:8080/people
{
  "_links" : {
    "search" : {
      "href" : "http://localhost:8080/people/search"
    }
  }
}

现在来创建一个新的 Person 对象!

$ curl -i -X POST -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, 05 Mar 2014 20:16:11 GMT
  • -i 确保您可以看到响应消息,包括头部。显示了新创建的 Person 的 URI

  • -X POST 发送一个 POST HTTP 请求来创建新条目

  • -H "Content-Type:application/json" 设置内容类型,以便应用知道请求体包含一个 JSON 对象

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

注意之前的 POST 操作如何包含一个 Location 头部。这包含了新创建资源的 URI。Spring Data REST 还在 RepositoryRestConfiguration 上有两个方法 setReturnBodyOnCreate(…)setReturnBodyOnCreate(…),您可以使用它们配置框架,使其在资源创建后立即返回该资源的表示形式。

从这里您可以查询所有的人

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

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,但在幕后,它正在与一个嵌入式的 Apache Geode 数据库进行通信。

在本指南中,只有一个域对象。在一个域对象相互关联的更复杂的系统中,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 调用来替换、更新或删除现有记录。

$ 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"
    }
  }
}
$ 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" : {
    "search" : {
      "href" : "http://localhost:8080/people/search"
    }
  }
}

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

总结

恭喜!您刚刚开发了一个具有基于超媒体的 RESTful 前端和基于 Apache Geode 后端的应用。

获取代码