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

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

你将构建什么

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

Spring Data REST 还支持将 Spring Data JPASpring Data GemfireSpring Data MongoDB 作为后端数据存储,但本指南主要讨论 Neo4j。

所需条件

如何完成本指南

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

从头开始,请继续阅读[scratch]

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

完成后,您可以在{project_id}/complete中查看代码以核对您的结果。

启动 Neo4j 服务器

在构建此应用程序之前,您需要设置一个 Neo4j 服务器。

Neo4j 提供了一个可以免费安装的开源服务器。

在安装了 Homebrew 的 Mac 上,您可以在终端窗口中键入以下命令:

$ brew install neo4j

有关其他选项,请参阅 https://neo4j.com/download/community-edition/

安装完 Neo4j 后,您可以通过运行以下命令使用默认设置启动它:

$ neo4j start

你应该会看到类似以下的消息:

Starting Neo4j.
Started neo4j (pid 96416). By default, it is available at http://localhost:7474/
There may be a short delay until the server is ready.
See /usr/local/Cellar/neo4j/3.0.6/libexec/logs/neo4j.log for current status.

默认情况下,Neo4j 的用户名和密码均为 neo4jneo4j。然而,它要求更改新账户的密码。为此,请运行以下命令:

$ curl -v -u neo4j:neo4j POST localhost:7474/user/neo4j/password -H "Content-type:application/json" -d "{\"password\":\"secret\"}"

这将密码从 neo4j 更改为 secret(在生产环境中千万不要这样做!)。完成后,您应该可以准备运行本指南了。

从 Spring Initializr 开始

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

要手动初始化项目:

  1. 访问 https://start.spring.io。该服务会为您拉取应用程序所需的所有依赖项,并完成大部分的设置工作。
  2. 选择 Gradle 或 Maven 以及您想要使用的语言。本指南假设您选择了 Java。
  3. 点击 Dependencies,然后选择 Rest RepositoriesSpring Data Neo4j
  4. 点击 Generate
  5. 下载生成的 ZIP 文件,这是一个根据您的选择配置好的 Web 应用程序归档文件。

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

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

访问 Neo4j 的权限

Neo4j 社区版需要凭据才能访问。您可以通过在 src/main/resources/application.properties 中设置属性来配置凭据,如下所示:

spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret

这包括默认用户名 (neo4j) 和您之前设置的新密码 (secret)。

切勿在您的源代码库中存储真实的凭据。相反,应通过使用Spring Boot 的属性覆盖在运行时配置它们。

创建领域对象

您需要创建一个新的领域对象来表示一个人,如下例所示(位于 src/main/java/com/example/accessingneo4jdatarest/Person.java 文件中):

package com.example.accessingneo4jdatarest;

import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.GeneratedValue;

@Node
public class Person {

  @Id @GeneratedValue 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/accessingneo4jdatarest/PersonRepository.java 中):

package com.example.accessingneo4jdatarest;

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 Initializr 创建一个项目时,它会生成一个应用程序类。您可以在 src/main/java/com/example/accessingneo4jdatarest/Application.java 中找到它。请注意,Spring Initializr 会将包名连接起来(并正确地更改大小写),并将其添加到 Application 中以生成应用程序的类名。在本例中,我们得到的类名是 AccessingNeo4jDataRestApplication,如下面的代码清单所示:

package com.example.accessingneo4jdatarest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableTransactionManagement
@EnableNeo4jRepositories
@SpringBootApplication
public class AccessingNeo4jDataRestApplication {

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

在本示例中,您无需对此应用程序类进行任何更改

@SpringBootApplication 是一个便捷的注解,它添加了以下所有内容:

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

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

  • @ComponentScan: 告诉 Spring 在 com/example 包中查找其他组件、配置和服务,使其能够找到控制器。

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

@EnableNeo4jRepositories 注解激活了 Spring Data Neo4j。Spring Data Neo4j 创建了 PersonRepository 的具体实现,并通过使用 Cypher 查询语言配置它与嵌入式 Neo4j 数据库进行通信。

构建可执行的 JAR 文件

您可以使用 Gradle 或 Maven 从命令行运行该应用程序。您还可以构建一个包含所有必要依赖项、类和资源的单个可执行 JAR 文件并运行它。构建可执行 JAR 文件使得在整个开发生命周期中,跨不同环境等场景下,更容易打包、版本控制和部署服务。

如果您使用 Gradle,可以通过 ./gradlew bootRun 运行应用程序。或者,您可以使用 ./gradlew build 构建 JAR 文件,然后运行该 JAR 文件,如下所示:

java -jar build/libs/{project_id}-0.1.0.jar

如果您使用的是 Maven,可以通过运行 ./mvnw spring-boot:run 来启动应用程序。或者,您也可以使用 ./mvnw clean package 构建 JAR 文件,然后像下面这样运行该 JAR 文件:

java -jar target/{project_id}-0.1.0.jar

这里描述的步骤可以创建一个可运行的 JAR 文件。您也可以构建一个经典的 WAR 文件

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

测试应用程序

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

首先,您可能想查看顶层服务。以下示例(包含输出)展示了如何操作:

$ 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
{
  "_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 -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/0
Content-Length: 0
Date: Wed, 26 Feb 2014 20:26:55 GMT
  • -i 确保您可以看到包括头部信息在内的响应消息。新创建的 Person 的 URI 会显示出来

  • -X POST 表示这是一个用于创建新条目的 POST 请求

  • -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" : {
    "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/0"
        }
      }
    } ]
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

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

您可以通过运行以下命令直接查询单个记录(显示其输出):

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

这看起来可能完全是基于 Web 的,但在幕后,有一个嵌入式 Neo4j 图数据库。在生产环境中,您可能会连接到一个独立的 Neo4j 服务器。

在本指南中,只有一个领域对象。在一个更为复杂的系统中,当领域对象相互关联时,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" : {
    "people" : [ {
      "firstName" : "Frodo",
      "lastName" : "Baggins",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/people/0"
        },
        "person" : {
          "href" : "http://localhost:8080/people/0"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/search/findByLastName?name=Baggins"
    }
  }
}

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

您还可以发出 PUTPATCHDELETE REST 调用来替换、更新或删除现有记录。以下示例(附带其输出)展示了一个 PUT 调用:

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

以下示例(附带其输出)展示了一个 PATCH 调用:

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

PUT 替换整个记录。未提供的字段将被替换为 nullPATCH 可用于更新部分项。

您还可以删除记录,如下例(显示其输出)所示:

$ curl -X DELETE http://localhost:8080/people/0
$ 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 前端和基于 Neo4j 的后端的应用程序。

另请参阅

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

本页目录