跳到主要内容
版本:8.1.1

Spring Data Neo4j 扩展

Doubao 中英对照 Spring Data Neo4j Extensions

可用于 Spring Data Neo4j 仓库的扩展​

Spring Data Neo4j 提供了若干可添加到仓库上的扩展(或称「混入」,mixin)。什么是混入?根据 Wikipedia 的定义,混入是一种语言概念,允许程序员将某些代码注入到一个类中。混入编程是一种软件开发风格:功能单元在一个类中被创建,然后与其他类混合在一起。

Java 在语言层面并不支持这一概念,但我们通过若干接口以及一个运行时来模拟它——该运行时会为这些接口添加相应的实现与拦截器。

默认添加的混入分别是 QueryByExampleExecutor 和 ReactiveQueryByExampleExecutor。这些接口在按示例查询中有详细说明。

此外还提供以下混入:

  • QuerydslPredicateExecutor

  • CypherdslConditionExecutor

  • CypherdslStatementExecutor

  • ReactiveQuerydslPredicateExecutor

  • ReactiveCypherdslConditionExecutor

  • ReactiveCypherdslStatementExecutor

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

QuerydslPredicateExecutor 与 CypherdslConditionExecutor 提供了相同的概念:由 SDN 生成一个查询,由您提供将被附加进去的「谓词」(Query DSL)或「条件」(Cypher DSL)。我们推荐使用 Cypher DSL,因为 SDN 原生就是用它来工作的。您甚至可以考虑使用注解处理器,它会为您生成一个静态元模型。

它是如何工作的?按前文所述声明您的仓库,并添加以下接口中的一个:

interface QueryDSLPersonRepository extends Neo4jRepository<Person, Long>, 1
QuerydslPredicateExecutor<Person> {

2

}

static class DtoPersonProjection {

private final String firstName;

DtoPersonProjection(String firstName) {
this.firstName = firstName;
}

String getFirstName() {
return this.firstName;
}

}
  • 标准的仓库声明

  • Query DSL 混入

或者

interface PersonRepository extends Neo4jRepository<Person, Long>, 1
CypherdslConditionExecutor<Person> {

2

}
  • 标准的仓库声明

  • 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(
this.firstName.eq(Cypher.anonParameter("Helge"))
.or(this.lastName.eq(Cypher.parameter("someName", "B."))), 3
this.lastName.descending() 4
)).extracting(Person::getFirstName).containsExactly("Helge", "Bela");
  • 定义一个具名的 Node 对象,指向查询的根节点

  • 从它派生出若干属性

  • 创建一个 or 条件。名字使用了一个匿名参数,姓氏使用了一个具名参数。这就是在这些片段中定义参数的方式,也是相比 Query DSL 混入的优势之一——后者做不到这一点。字面量可以用 Cypher.literalOf 来表达。

  • 从某个属性定义一个 SortItem

Query DSL 混入的代码看起来也相当相似。选择 Query DSL 混入的理由可能是对其 API 更熟悉,以及它也适用于其他存储。而不选它的理由则包括:您需要在 classpath 上额外引入一个库、它缺少对遍历关系的支持,以及前面提到的、它在谓词中不支持参数(严格来说它是支持的,但并没有 API 方法能把这些参数实际传递给正在执行的查询)。

对实体与投影使用(动态)Cypher-DSL 语句​

添加相应的混入与使用条件执行器没有什么不同:

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

}

当您扩展 ReactiveNeo4jRepository 时,请使用 ReactiveCypherdslStatementExecutor。

CypherdslStatementExecutor 为 findOne 和 findAll 提供了若干重载。它们都以一个 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");
});
}
  • 动态查询在一个辅助方法中以类型安全的方式构建

  • 我们在此处已经见过这种写法,那里也定义了若干持有模型的变量

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

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

  • 或一个投影。

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