使用 Neo4j 访问数据

本指南将引导您完成使用 Spring Data Neo4j 构建应用程序的过程,该应用程序将数据存储在 Neo4j(一个基于图形的数据库)中并从中检索数据。

您将构建什么

您将使用 Neo4j 的 NoSQL 基于图形的数据存储构建嵌入式 Neo4j 服务器、存储实体和关系以及开发查询。

您需要什么

如何完成本指南

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

从头开始,请继续执行 从 Spring Initializr 开始

跳过基础知识,请执行以下操作

完成后,您可以将您的结果与 gs-accessing-data-neo4j/complete 中的代码进行对比。

从 Spring Initializr 开始

您可以使用此 预初始化项目 并单击“生成”以下载 ZIP 文件。此项目已配置为适合本教程中的示例。

要手动初始化项目

  1. 导航到 https://start.spring.io。此服务会引入应用程序所需的所有依赖项,并为您完成大部分设置工作。

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

  3. 单击“依赖项”并选择“Spring Data Neo4j”。

  4. 单击“生成”。

  5. 下载生成的 ZIP 文件,该文件是使用您的选择配置的 Web 应用程序的存档。

如果您的 IDE 集成了 Spring Initializr,则可以从 IDE 中完成此过程。
您也可以从 GitHub 分叉项目并在您的 IDE 或其他编辑器中打开它。

启动 Neo4j 服务器

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

Neo4j 有一个您可以免费安装的开源服务器,或者您可以使用 Docker 运行它。

要在安装了 Homebrew 的 Mac 上安装服务器,请运行以下命令

$ brew install neo4j

有关其他选项,请访问 https://neo4j.ac.cn/download/community-edition/

安装完成后,通过运行以下命令以其默认设置启动它

$ neo4j start

您应该会看到类似于以下内容的输出

Starting Neo4j.
Started neo4j (pid 96416). By default, it is available at https://127.0.0.1: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——在生产环境中不要这样做!完成此步骤后,您应该就可以运行本指南的其余部分了。

或者,要使用 Neo4j Docker 镜像 运行。您可以使用 NEO4J_AUTH 环境变量更改密码。

docker run \
  --publish=7474:7474 --publish=7687:7687 \
  --volume=$HOME/neo4j/data:/data \
  --env NEO4J_AUTH=neo4j/password
  neo4j

定义简单实体

Neo4j 捕获实体及其关系,这两个方面同等重要。假设您正在对一个系统进行建模,在该系统中,您为每个人存储一条记录。但是,您还希望跟踪一个人的同事(在本例中为 teammates)。使用 Spring Data Neo4j,您可以使用一些简单的注释捕获所有这些内容,如下面的列表(在 src/main/java/com/example/accessingdataneo4j/Person.java 中)所示

package com.example.accessingdataneo4j;

import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

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

@Node
public class Person {

  @Id @GeneratedValue private Long id;

  private String name;

  private Person() {
    // Empty constructor required as of Neo4j API 2.0.5
  };

  public Person(String name) {
    this.name = name;
  }

  /**
   * Neo4j doesn't REALLY have bi-directional relationships. It just means when querying
   * to ignore the direction of the relationship.
   * https://dzone.com/articles/modelling-data-neo4j
   */
  @Relationship(type = "TEAMMATE")
  public Set<Person> teammates;

  public void worksWith(Person person) {
    if (teammates == null) {
      teammates = new HashSet<>();
    }
    teammates.add(person);
  }

  public String toString() {

    return this.name + "'s teammates => "
      + Optional.ofNullable(this.teammates).orElse(
          Collections.emptySet()).stream()
            .map(Person::getName)
            .collect(Collectors.toList());
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

这里您有一个 Person 类,它只有一个属性:name

Person 类用 @NodeEntity 进行了注释。当 Neo4j 存储它时,会创建一个新节点。此类还具有一个标记为 @GraphIdid。Neo4j 在内部使用 @GraphId 来跟踪数据。

下一个重要部分是 teammates 集合。它是一个简单的 Set<Person>,但被标记为 @Relationship。这意味着此集合的每个成员都应作为单独的 Person 节点存在。请注意方向是如何设置为 UNDIRECTED 的。这意味着当您查询 TEAMMATE 关系时,Spring Data Neo4j 会忽略关系的方向。

使用 worksWith() 方法,您可以轻松地将人员链接在一起。

最后,您有一个方便的 toString() 方法来打印出人员的姓名和该人员的同事。

创建简单查询

Spring Data Neo4j 专注于将数据存储在 Neo4j 中。但它继承了 Spring Data Commons 项目的功能,包括派生查询的能力。从本质上讲,您无需学习 Neo4j 的查询语言。相反,您可以编写少量方法,并让查询为您编写。

要了解其工作原理,请创建一个查询 Person 节点的接口。下面的列表(在 src/main/java/com/example/accessingdataneo4j/PersonRepository.java 中)显示了这样的查询

package com.example.accessingdataneo4j;

import java.util.List;
import org.springframework.data.neo4j.repository.Neo4jRepository;

public interface PersonRepository extends Neo4jRepository<Person, Long> {

  Person findByName(String name);
  List<Person> findByTeammatesName(String name);
}

PersonRepository 扩展了 Neo4jRepository 接口并插入其操作的类型:Person。此接口附带许多操作,包括标准 CRUD(创建、读取、更新和删除)操作。

但是您可以通过声明其方法签名来定义其他查询。在本例中,您添加了 findByName,它搜索类型为 Person 的节点并查找与 name 匹配的节点。您还有 findByTeammatesName,它查找 Person 节点,深入 teammates 字段的每个条目,并根据队友的 name 进行匹配。

访问 Neo4j 的权限

Neo4j 社区版需要凭据才能访问它。您可以通过设置几个属性(在 src/main/resources/application.properties 中)来配置这些凭据,如下面的列表所示

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

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

不要将真实凭据存储在源代码存储库中。相反,请使用 Spring Boot 的属性覆盖 在运行时配置它们。

有了这个,您可以将其连接起来并看看它是什么样子!

创建应用程序类

Spring Initializr 为应用程序创建一个简单的类。下面的列表显示了 Initializr 为此示例创建的类(在 src/main/java/com/example/accessingdataneo4j/AccessingDataNeo4jApplication.java 中)

package com.example.accessingdataneo4j;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AccessingDataNeo4jApplication {

  public static void main(String[] args) {
    SpringApplication.run(AccessingDataNeo4jApplication.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,您不必处理配置任何管道或基础设施。

只要这些存储库包含在您的 @SpringBootApplication 类的同一包(或子包)中,Spring Boot 就会自动处理它们。要更详细地控制注册过程,可以使用 @EnableNeo4jRepositories 注释。

默认情况下,@EnableNeo4jRepositories 会扫描当前包以查找扩展 Spring Data 的任何存储库接口的接口。如果您的项目布局有多个项目并且它找不到您的存储库,您可以使用其 basePackageClasses=MyRepository.class 通过类型安全地告诉 Spring Data Neo4j 扫描不同的根包。

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

现在自动装配您之前定义的 PersonRepository 的实例。Spring Data Neo4j 动态地实现该接口并插入所需的查询代码以满足该接口的义务。

main 方法使用 Spring Boot 的 SpringApplication.run() 启动应用程序并调用构建关系的 CommandLineRunner

在本例中,您创建了三个本地 Person 实例:Greg、Roy 和 Craig。最初,它们仅存在于内存中。请注意,还没有人是任何人的队友。

首先,您找到 Greg,表明他与 Roy 和 Craig 合作,然后再次持久化他。请记住,队友关系被标记为 UNDIRECTED(即双向)。这意味着 Roy 和 Craig 也已更新。

这就是为什么当您需要更新 Roy 时。至关重要的是,您必须首先从 Neo4j 中获取该记录。在将 Craig 添加到列表之前,您需要 Roy 的队友的最新状态。

为什么没有获取 Craig 并添加任何关系的代码?因为您已经有了!Greg 之前将 Craig 标记为队友,Roy 也是如此。这意味着无需再次更新 Craig 的关系。当您遍历每个团队成员并将他们的信息打印到控制台时,您可以看到它。

最后,查看您向后查找的其他查询,回答“谁与谁合作?”的问题。

下面的列表显示了已完成的 AccessingDataNeo4jApplication 类(位于 src/main/java/com/example/accessingdataneo4j/AccessingDataNeo4jApplication.java 中)

package com.example.accessingdataneo4j;

import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;

@SpringBootApplication
@EnableNeo4jRepositories
public class AccessingDataNeo4jApplication {

	private final static Logger log = LoggerFactory.getLogger(AccessingDataNeo4jApplication.class);

	public static void main(String[] args) throws Exception {
		SpringApplication.run(AccessingDataNeo4jApplication.class, args);
		System.exit(0);
	}

	@Bean
	CommandLineRunner demo(PersonRepository personRepository) {
		return args -> {

			personRepository.deleteAll();

			Person greg = new Person("Greg");
			Person roy = new Person("Roy");
			Person craig = new Person("Craig");

			List<Person> team = Arrays.asList(greg, roy, craig);

			log.info("Before linking up with Neo4j...");

			team.stream().forEach(person -> log.info("\t" + person.toString()));

			personRepository.save(greg);
			personRepository.save(roy);
			personRepository.save(craig);

			greg = personRepository.findByName(greg.getName());
			greg.worksWith(roy);
			greg.worksWith(craig);
			personRepository.save(greg);

			roy = personRepository.findByName(roy.getName());
			roy.worksWith(craig);
			// We already know that roy works with greg
			personRepository.save(roy);

			// We already know craig works with roy and greg

			log.info("Lookup each person by name...");
			team.stream().forEach(person -> log.info(
					"\t" + personRepository.findByName(person.getName()).toString()));

			List<Person> teammates = personRepository.findByTeammatesName(greg.getName());
			log.info("The following have Greg as a teammate...");
			teammates.stream().forEach(person -> log.info("\t" + person.getName()));
		};
	}

}

构建可执行 JAR

您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必要依赖项、类和资源的单个可执行 JAR 文件,然后运行它。构建可执行 JAR 文件可以轻松地在整个开发生命周期中、跨不同环境等方面交付、版本化和部署服务作为应用程序。

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

java -jar build/libs/gs-accessing-data-neo4j-0.1.0.jar

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

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

您应该会看到类似以下列表的内容(以及其他内容,例如查询):

Before linking up with Neo4j...
	Greg's teammates => []
	Roy's teammates => []
	Craig's teammates => []

Lookup each person by name...
	Greg's teammates => [Roy, Craig]
	Roy's teammates => [Greg, Craig]
	Craig's teammates => [Roy, Greg]

您可以从输出中看到(最初)没有人通过任何关系连接。然后,在您添加人员后,他们会联系在一起。最后,您可以看到方便的查询,该查询根据队友查找人员。

总结

恭喜!您刚刚设置了一个嵌入式 Neo4j 服务器,存储了一些简单的相关实体,并开发了一些快速查询。

如果您想轻松地使用基于超媒体的 RESTful 前端公开 Neo4j 存储库,请阅读使用 REST 访问 Neo4j 数据

另请参阅

以下指南可能也有帮助

想要编写新的指南或为现有指南做出贡献?查看我们的贡献指南

所有指南都使用代码的 ASLv2 许可证和署名-非衍生作品创作共用许可证发布写作。

获取代码