快速上手
我们为 SDN 提供了一个 Spring Boot 起步依赖。请通过您的依赖管理引入该起步模块,并配置要使用的 bolt URL,例如 spring.neo4j.uri=bolt://localhost:7687。该起步依赖假定服务器已禁用身份验证。由于 SDN 起步依赖依赖 Java 驱动的起步依赖,那里关于配置的所有说明在这里同样适用。有关可用属性的参考,请在 spring.neo4j 命名空间下使用您 IDE 的自动补全。
SDN 支持
-
广为人知、易于理解的命令式编程模型(很像 Spring Data JDBC 或 JPA)
-
基于 Reactive Streams 的响应式编程,包括对响应式事务的全面支持。
这些都包含在同一个二进制文件中。响应式编程模型在数据库侧需要 4+ 的 Neo4j 服务器,另一方面需要响应式的 Spring。
准备数据库
在本例中,我们使用电影图,因为每个 Neo4j 实例都自带它。
如果您没有正在运行的数据库但安装了 Docker,请运行:
docker run --publish=7474:7474 --publish=7687:7687 -e 'NEO4J_AUTH=neo4j/secret' neo4j:5
现在您可以访问 http://localhost:7474。上面的命令将服务器密码设置为 secret。请注意提示符中准备好运行的命令(:play movies)。执行它即可用一些测试数据填充您的数据库。
创建一个新的 Spring Boot 项目
搭建 Spring Boot 项目最简单的方式是 start.spring.io(它也集成在主流 IDE 中,如果您不想使用网站的话)。
选择 "Spring Web Starter" 以获得创建基于 Spring 的 Web 应用所需的全部依赖。Spring Initializr 会为您创建有效的项目结构,为所选构建工具备好所有文件和设置。
使用 Maven
您可以向 Spring Initializer 发出一个 curl 请求来创建一个基本的 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。由于该起步依赖尚未出现在 initializer 中,您需要手动将以下依赖添加到您的 pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
如果是既有项目,您同样需要手动添加该依赖。
使用 Gradle
思路相同,只需生成一个 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:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-neo4j'
}
如果是既有项目,您同样需要手动添加该依赖。
配置项目
现在在您喜欢的 IDE 中打开其中任一项目。找到 application.properties 并配置您的 Neo4j 凭据:
spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=verysecret
这是连接到 Neo4j 实例所需的最低配置。
使用该起步依赖时,无需添加任何对驱动的编程式配置。SDN 仓库会由此起步依赖自动启用。
配置 Neo4j Cypher-DSL
根据您运行应用所使用的 Neo4j 版本,建议配置 Neo4j Cypher-DSL 运行所采用的方言。默认使用的方言以 Neo4j 5 作为 Neo4j 的 LTS 版本为目标。它兼容 Neo4j 5.23+ 和 Neo4j 2025.x。可以通过定义一个 Cypher-DSL Configuration bean 来更改。
@Bean
Configuration cypherDslConfiguration() {
return Configuration.newConfig()
.withDialect(Dialect.NEO4J_4).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 如下:
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
}
Spring Data Neo4j 使用 Spring Data Commons 及其反射能力,因此您至少需要向
spring.core开放您的领域包。这里我们假设
your.domain中还包含仓库:这些必须导出,才能被spring.beans、spring.context和spring.data.commons访问。如果您不想把它们导出给全局,可以将访问权限限制为这些模块。
创建您的领域
我们的领域层应完成两件事:
-
将您的图映射为对象
-
提供对这些对象的访问
示例节点实体
SDN 完全支持不可变实体,无论是 Java 还是 Kotlin 中的 data 类。因此,这里我们将重点放在不可变实体上,MovieEntity.java 展示了这样一个实体。
SDN 支持 Neo4j Java 驱动支持的所有数据类型,参见“Cypher 类型系统”一章中的将 Neo4j 类型映射为原生语言类型。未来版本将支持更多转换器。
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
}
@Node用于将该类标记为受管实体。它也用于配置 Neo4j 标签。如果您只使用普通的@Node,标签默认为类名。每个实体都必须有一个 id。这里展示的 movie 类使用属性
title作为唯一业务键。如果您没有这样的唯一键,可以使用@Id与@GeneratedValue的组合来配置 SDN 使用 Neo4j 的内部 id。我们还提供 UUID 生成器。这里展示了
@Property,作为为字段使用与图属性不同名称的一种方式。这里定义了到类型为
PersonEntity的类的关系,以及关系类型ACTED_IN这是供您应用代码使用的构造函数。
作为一般性说明:使用内部生成 id 的不可变实体多少有些矛盾,因为 SDN 需要一种方式来用数据库生成的值设置该字段。
如果您找不到合适的业务键,或不想为 id 使用生成器,下面是同一个实体,它使用内部生成 id,并配合一个常规构造函数和一个所谓的 wither 方法,供 SDN 使用:
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;
}
}
}
这是供您应用代码使用的构造函数。它将 id 设置为 null,因为包含内部 id 的字段绝不应被修改。
这是针对
id属性的所谓 wither。它创建一个新实体并相应设置该字段,而不修改原实体,从而使其不可变。
您当然可以将 SDN 与 Kotlin 一起使用,并用 Kotlin 的数据类为您的领域建模。如果您希望或需要纯粹使用 Java,Project Lombok 是一个替代方案。
声明 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 的一部分。大多数时候,去掉某些东西比之后再添加更难。此外,使用存储特定的内容会把您的存储泄漏到领域中。从性能角度看,这没有任何损失。
一个适配上述任一 movie 实体的响应式仓库如下所示:
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 的相应文档,或查看我们的示例代码。