开始
我们为 SDN 提供一个 Spring Boot starter。请通过您的依赖管理引入 starter 模块,并配置要使用的 bolt URL,例如 spring.neo4j.uri=bolt://:7687。该 starter 假设服务器已禁用身份验证。由于 SDN starter 依赖于 Java Driver 的 starter,因此所有关于配置的内容也适用于此处。有关可用属性的参考,请在 spring.neo4j 命名空间中使用您的 IDE 自动补全功能。
SDN 支持
-
众所周知且易于理解的命令式编程模型(与 Spring Data JDBC 或 JPA 非常相似)
-
基于 Reactive Streams 的响应式编程,包括对响应式事务的全面支持。
所有这些都包含在同一个二进制文件中。响应式编程模型需要在数据库端使用 Neo4j 4+ 服务器,并在另一端使用响应式 Spring。
准备数据库
对于本例,我们继续使用 电影图,因为它随每个 Neo4j 实例免费提供。
如果您没有运行中的数据库但已安装 Docker,请运行
docker run --publish=7474:7474 --publish=7687:7687 -e 'NEO4J_AUTH=neo4j/secret' neo4j:5
您现在可以访问 https://: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。由于此 starter 尚未在初始化器中,您需要手动将以下依赖项添加到您的 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://:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=verysecret
这是连接到 Neo4j 实例所需的最低配置。
| 使用此 starter 时,无需对驱动程序进行任何编程配置。SDN 存储库将由此 starter 自动启用。 |
配置 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-info.javamodule 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.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
}
| 1 | @Node 用于将此类标记为受管理实体。它也用于配置 Neo4j 标签。如果只使用普通的 @Node,则标签默认为类名。 |
| 2 | 每个实体都必须有一个 ID。这里显示的电影类使用属性 title 作为唯一的业务键。如果您没有这样的唯一键,可以使用 @Id 和 @GeneratedValue 的组合来配置 SDN 以使用 Neo4j 的内部 ID。我们还提供 UUID 的生成器。 |
| 3 | 这展示了 @Property 作为一种为字段使用不同于图属性名称的方法。 |
| 4 | 这定义了一个与 PersonEntity 类型的类以及关系类型 ACTED_IN 的关系 |
| 5 | 这是您的应用程序代码要使用的构造函数。 |
作为一般性说明:使用内部生成 ID 的不可变实体有点矛盾,因为 SDN 需要一种方法来设置数据库生成的值的字段。
如果您找不到一个好的业务键或不想使用 ID 生成器,这里是使用内部生成 ID 的相同实体,以及一个常规构造函数和一个由 SDN 使用的所谓 wither-方法
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 的数据类来建模您的领域。如果您想或需要纯粹停留在 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 的一部分。大多数情况下,删除东西比以后添加东西更难。此外,使用特定于存储的方法会将您的存储泄漏到您的领域中。从性能角度来看,没有损失。
适用于上述任何电影实体的响应式存储库如下所示
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 的相应文档或参阅我们的示例代码。 |