跳到主要内容

Spring Data Neo4j 扩展

DeepSeek V3 中英对照 Spring Data Neo4j Extensions

Spring Data Neo4j 仓库的可用扩展

Spring Data Neo4j 提供了一些可以添加到存储库中的扩展或“mixin”。什么是 mixin?根据 Wikipedia 的解释,mixin 是一种语言概念,允许程序员将一些代码注入到类中。Mixin 编程是一种软件开发风格,其中功能单元在类中创建,然后与其他类混合在一起。

Java 在语言层面并不支持这一概念,但我们通过几个接口和一个运行时来模拟它,该运行时添加了适当的实现和拦截器。

默认添加的 Mixins 分别是 QueryByExampleExecutorReactiveQueryByExampleExecutor。这些接口的详细解释请参见 按示例查询

提供的额外 mixins 包括:

  • QuerydslPredicateExecutor

  • CypherdslConditionExecutor

  • CypherdslStatementExecutor

  • ReactiveQuerydslPredicateExecutor

  • ReactiveCypherdslConditionExecutor

  • ReactiveCypherdslStatementExecutor

为生成的查询添加动态条件

QuerydslPredicateExecutorCypherdslConditionExecutor 提供了相同的概念:SDN 生成查询,你提供“谓词”(Query DSL)或“条件”(Cypher DSL)来添加到查询中。我们推荐使用 Cypher DSL,因为这是 SDN 原生使用的。你甚至可以考虑使用注解处理器,它会为你生成一个静态元模型。

它是如何工作的?如上所述声明你的仓库,并添加以下一个接口:

interface QueryDSLPersonRepository extends
Neo4jRepository<Person, Long>, 1
QuerydslPredicateExecutor<Person> { 2
}
java
  • 标准仓库声明

  • Query DSL 混合

import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.support.CypherdslConditionExecutor;

interface PersonRepository extends
Neo4jRepository<Person, Long>, 1
CypherdslConditionExecutor<Person> { 2
}
java
  • 标准仓库声明

  • Cypher DSL 混入

以下是使用 Cypher DSL 条件执行器的示例用法:

Node person = Cypher.node("Person").named("person"); 1
Property firstName = person.property("firstName"); 2
Property lastName = person.property("lastName");

assertThat(
repository.findAll(
firstName.eq(Cypher.anonParameter("Helge"))
.or(lastName.eq(Cypher.parameter("someName", "B."))), 3
lastName.descending() 4
))
.extracting(Person::getFirstName)
.containsExactly("Helge", "Bela");
java
  • 定义一个名为 Node 的对象,目标为查询的根节点

  • 从中派生一些属性

  • 创建一个 or 条件。匿名参数用于名字,命名参数用于姓氏。这是在片段中定义参数的方式,也是相对于 Query-DSL mixin 的优势之一,后者无法做到这一点。字面量可以通过 Cypher.literalOf 表示。

  • 从其中一个属性定义一个 SortItem

对于 Query-DSL mixin 的代码看起来非常相似。使用 Query-DSL mixin 的原因可能是 API 的熟悉度以及它也可以与其他存储一起工作。反对它的原因是,你需要在类路径上添加一个额外的库,它缺少对遍历关系的支持,以及上述提到的它不支持谓词中的参数(技术上支持,但没有 API 方法将参数实际传递给正在执行的查询)。

使用(动态)Cypher-DSL 语句进行实体和投影

添加相应的 mixin 与使用 条件执行器 并无不同:

interface PersonRepository extends
Neo4jRepository<Person, Long>,
CypherdslStatementExecutor<Person> {
}
java

在扩展 ReactiveNeo4jRepository 时,请使用 ReactiveCypherdslStatementExecutor

CypherdslStatementExecutor 提供了多个 findOnefindAll 的重载方法。这些方法都以 Cypher-DSL 语句或该语句的持续定义作为第一个参数,对于投影方法,还需要一个类型参数。

如果查询需要参数,必须通过 Cypher-DSL 本身定义并填充,如下面的代码清单所示:

static Statement whoHasFirstNameWithAddress(String name) { 1
Node p = Cypher.node("Person").named("p"); 2
Node a = Cypher.anyNode("a");
Relationship r = p.relationshipTo(a, "LIVES_AT");
return Cypher.match(r)
.where(p.property("firstName").isEqualTo(Cypher.anonParameter(name))) 3
.returning(
p.getRequiredSymbolicName(),
Cypher.collect(r),
Cypher.collect(a)
)
.build();
}

@Test
void fineOneShouldWork(@Autowired PersonRepository repository) {

Optional<Person> result = repository.findOne(whoHasFirstNameWithAddress("Helge")); 4

assertThat(result).hasValueSatisfying(namesOnly -> {
assertThat(namesOnly.getFirstName()).isEqualTo("Helge");
assertThat(namesOnly.getLastName()).isEqualTo("Schneider");
assertThat(namesOnly.getAddress()).extracting(Person.Address::getCity)
.isEqualTo("Mülheim an der Ruhr");
});
}

@Test
void fineOneProjectedShouldWork(@Autowired PersonRepository repository) {

Optional<NamesOnly> result = repository.findOne(
whoHasFirstNameWithAddress("Helge"),
NamesOnly.class 5
);

assertThat(result).hasValueSatisfying(namesOnly -> {
assertThat(namesOnly.getFirstName()).isEqualTo("Helge");
assertThat(namesOnly.getLastName()).isEqualTo("Schneider");
assertThat(namesOnly.getFullName()).isEqualTo("Helge Schneider");
});
}
java
  • 动态查询是在一个辅助方法中以类型安全的方式构建的

  • 我们已经在这里看到过这一点,在那里我们还定义了一些保存模型的变量

  • 我们定义了一个匿名参数,由传递给方法的 name 的实际值填充

  • 从辅助方法返回的语句用于查找实体

  • 或者一个投影。

findAll 方法的工作方式类似。命令式的 Cypher-DSL 语句执行器还提供了一个返回分页结果的重载方法。