入门指南

我们为 SDN 提供了一个 Spring Boot 启动器。请通过您的依赖项管理包含启动器模块,并配置要使用的 Bolt URL,例如 spring.neo4j.uri=bolt://127.0.0.1:7687。启动器假设服务器已禁用身份验证。由于 SDN 启动器依赖于 Java 驱动程序的启动器,因此此处所述的所有关于配置的内容也适用于此。有关可用属性的参考,请在 spring.neo4j 命名空间中使用 IDE 的自动完成功能。

SDN 支持

  • 众所周知且易于理解的命令式编程模型(非常类似于 Spring Data JDBC 或 JPA)

  • 基于 Reactive Streams 的响应式编程,包括对 响应式事务 的全面支持。

这些都包含在同一个二进制文件中。响应式编程模型需要数据库端的 4+ 版本的 Neo4j 服务器以及另一端的响应式 Spring。

准备数据库

在本例中,我们使用 电影图,因为它在每个 Neo4j 实例中都是免费提供的。

如果您没有正在运行的数据库但安装了 Docker,请运行以下命令:

在 Docker 中启动一个本地 Neo4j 实例。
docker run --publish=7474:7474 --publish=7687:7687 -e 'NEO4J_AUTH=neo4j/secret' neo4j:5

您现在可以访问 https://127.0.0.1:7474。上述命令将服务器的密码设置为 secret。请注意提示符中准备运行的命令(:play movies)。执行它以使用一些测试数据填充您的数据库。

创建一个新的 Spring Boot 项目

设置 Spring Boot 项目最简单的方法是使用 start.spring.io(它也集成在主要的 IDE 中,如果您不想使用网站)。

选择“Spring Web Starter”以获取创建基于 Spring 的 Web 应用程序所需的所有依赖项。Spring Initializr 将负责为您创建一个有效的项目结构,其中包含所有文件和为所选构建工具设置的设置。

使用 Maven

您可以对 Spring Initializr 发出 curl 请求以创建一个基本的 Maven 项目

使用 Spring Initializr 创建一个基本的 Maven 项目
curl https://start.spring.io/starter.tgz \
  -d dependencies=webflux,data-neo4j  \
  -d bootVersion=3.2.0 \
  -d baseDir=Neo4jSpringBootExample \
  -d name=Neo4j%20SpringBoot%20Example | tar -xzvf -

这将创建一个新的文件夹 Neo4jSpringBootExample。由于此启动器尚未在初始化程序中,因此您必须手动将以下依赖项添加到您的 pom.xml

在 Maven 项目中包含 spring-data-neo4j-spring-boot-starter
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>

如果您已有项目,也可以手动添加依赖项。

使用 Gradle

思路相同,只需生成一个 Gradle 项目即可

使用 Spring Initializr 创建一个基本的 Gradle 项目
curl https://start.spring.io/starter.tgz \
  -d dependencies=webflux,data-neo4j \
  -d type=gradle-project \
  -d bootVersion=3.2.0 \
  -d baseDir=Neo4jSpringBootExampleGradle \
  -d name=Neo4j%20SpringBoot%20Example | tar -xzvf -

Gradle 的依赖项如下所示,必须将其添加到 build.gradle

在 Gradle 项目中包含 spring-data-neo4j-spring-boot-starter
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-neo4j'
}

如果您已有项目,也可以手动添加依赖项。

配置项目

现在在您喜欢的 IDE 中打开这些项目中的任意一个。找到 application.properties 并配置您的 Neo4j 凭据

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

这是连接到 Neo4j 实例所需的最低限度的配置。

当您使用此启动器时,无需添加任何驱动程序的编程配置。SDN 仓库将由此启动器自动启用。

配置 Neo4j Cypher-DSL

根据您运行应用程序的 Neo4j 版本,建议配置 Neo4j Cypher-DSL 运行的方言。使用的默认方言是针对 Neo4j 4.4 的,因为它是 Neo4j 的 LTS 版本。这可以通过定义一个 Cypher-DSL Configuration bean 来更改。

使 Cypher-DSL 使用 Neo4j 5 方言
@Bean
Configuration cypherDslConfiguration() {
	return Configuration.newConfig()
                .withDialect(Dialect.NEO4J_5).build();
}
尽管 Spring Data Neo4j 尽最大努力兼容 Neo4j 5 和默认方言的组合,但始终建议明确定义方言。例如,这将导致更优化的查询并为更新的 Neo4j 版本利用 elementId()

在模块路径上运行

Spring Data Neo4j 可以在模块路径上运行。它的自动模块名称为 spring.data.neo4j。由于当前 Spring Data 构建设置中的限制,它本身不提供模块。因此,它使用一个自动但稳定的模块名称。但是,它确实依赖于一个模块化的库(Cypher-DSL)。由于上述限制,如果没有 module-info.java,我们无法代表您表达对该库的要求。

因此,在您的项目中,要在模块路径上运行 Spring Data Neo4j 6.1+,所需的最小 module-info.java 如下所示

在打算在模块路径上使用 Spring Data Neo4j 的项目中的 module-info.java
module your.module {

	requires org.neo4j.cypherdsl.core;

	requires spring.data.commons;
	requires spring.data.neo4j;

	opens your.domain to spring.core; (1)

	exports your.domain; (2)
}
1 Spring Data Neo4j 使用 Spring Data Commons 及其反射功能,因此您至少需要将您的域包打开到 spring.core
2 这里我们假设your.domain也包含存储库:这些存储库必须导出才能被spring.beansspring.contextspring.data.commons访问。如果您不想将其导出到外部,可以将其限制在这些模块中。

创建您的领域

我们的领域层应该完成两件事

  • 将您的图映射到对象

  • 提供对它们的访问

示例节点实体

SDN 完全支持不可修改的实体,包括 Java 和 Kotlin 中的data类。因此,我们将重点介绍不可变实体,MovieEntity.java 展示了这样一个实体。

SDN 支持 Neo4j Java 驱动程序支持的所有数据类型,请参阅“Cypher 类型系统”章节中的将 Neo4j 类型映射到原生语言类型。未来版本将支持其他转换器。
MovieEntity.java
import java.util.ArrayList;
import java.util.List;

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.Relationship.Direction;

@Node("Movie") (1)
public class MovieEntity {

	@Id (2)
	private final String title;

	@Property("tagline") (3)
	private final String description;

	@Relationship(type = "ACTED_IN", direction = Direction.INCOMING) (4)
	private List<Roles> actorsAndRoles = new ArrayList<>();

	@Relationship(type = "DIRECTED", direction = Direction.INCOMING)
	private List<PersonEntity> directors = new ArrayList<>();

	public MovieEntity(String title, String description) { (5)
		this.title = title;
		this.description = description;
	}

	// Getters omitted for brevity
}
1 @Node 用于将此类标记为托管实体。它还用于配置 Neo4j 标签。如果您只是使用普通的@Node,则标签默认为类的名称。
2 每个实体都必须具有一个 ID。此处显示的电影类使用属性title作为唯一的业务键。如果您没有这样的唯一键,则可以使用@Id@GeneratedValue的组合来配置 SDN 以使用 Neo4j 的内部 ID。我们还提供了 UUID 生成器。
3 这展示了@Property作为一种方法,用于为字段使用与图属性不同的名称。
4 这定义了一个到类型为PersonEntity的类和关系类型ACTED_IN的关系。
5 这是您的应用程序代码要使用的构造函数。

一般来说:使用内部生成的 ID 的不可变实体有点矛盾,因为 SDN 需要一种方法来使用数据库生成的字段值。

如果您找不到合适的业务键或不想使用 ID 生成器,以下是使用内部生成的 ID 以及常规构造函数和所谓的wither方法的相同实体,该方法由 SDN 使用

MovieEntity.java
import org.springframework.data.neo4j.core.schema.GeneratedValue;
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.annotation.PersistenceConstructor;

@Node("Movie")
public class MovieEntity {

	@Id @GeneratedValue
	private Long id;

	private final String title;

	@Property("tagline")
	private final String description;

	public MovieEntity(String title, String description) { (1)
		this.id = null;
		this.title = title;
		this.description = description;
	}

	public MovieEntity withId(Long id) { (2)
		if (this.id.equals(id)) {
			return this;
		} else {
			MovieEntity newObject = new MovieEntity(this.title, this.description);
			newObject.id = id;
			return newObject;
		}
	}
}
1 这是您的应用程序代码要使用的构造函数。它将 id 设置为 null,因为包含内部 id 的字段永远不应该被修改。
2 这是id属性的所谓wither。它创建一个新的实体并相应地设置字段,而不会修改原始实体,从而使其保持不变。

当然,您可以将 SDN 与Kotlin一起使用,并使用 Kotlin 的数据类对您的领域进行建模。Project Lombok 是一种替代方案,如果您希望或需要完全在 Java 中使用。

声明 Spring Data 存储库

您基本上有两个选择:您可以使用 SDN 以存储库无关的方式工作,并使您的特定领域扩展以下其中之一

  • org.springframework.data.repository.Repository

  • org.springframework.data.repository.CrudRepository

  • org.springframework.data.repository.reactive.ReactiveCrudRepository

  • org.springframework.data.repository.reactive.ReactiveSortingRepository

根据需要选择命令式和响应式。

虽然从技术上讲没有禁止,但建议不要在同一个应用程序中混合使用命令式和响应式数据库访问。我们不会支持这种情况。

另一种选择是选择特定于存储的实现,并获得我们开箱即用的所有方法。这种方法的优势也是其最大的劣势:一旦发布,所有这些方法都将成为您 API 的一部分。大多数情况下,删除某些东西比之后添加东西更难。此外,使用特定于存储库的功能会将存储库泄漏到您的领域中。从性能的角度来看,没有任何损失。

一个适合上面任何电影实体的响应式存储库如下所示

MovieRepository.java
import reactor.core.publisher.Mono;

import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;

public interface MovieRepository extends ReactiveNeo4jRepository<MovieEntity, String> {

	Mono<MovieEntity> findOneByTitle(String title);
}
响应式代码的测试使用reactor.test.StepVerifier完成。请查看相应的Project Reactor 文档或查看我们的示例代码。