Spring Data Neo4j 扩展
Spring Data Neo4j 仓库的可用扩展
Spring Data Neo4j 提供了一些可以添加到存储库中的扩展或“mixin”。什么是 mixin?根据 Wikipedia 的解释,mixin 是一种语言概念,允许程序员将一些代码注入到类中。Mixin 编程是一种软件开发风格,其中功能单元在类中创建,然后与其他类混合在一起。
Java 在语言层面并不支持这一概念,但我们通过几个接口和一个运行时来模拟它,该运行时添加了适当的实现和拦截器。
默认添加的 Mixins 分别是 QueryByExampleExecutor
和 ReactiveQueryByExampleExecutor
。这些接口的详细解释请参见 按示例查询。
提供的额外 mixins 包括:
-
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
}
标准仓库声明
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
}
标准仓库声明
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");
定义一个名为
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> {
}
在扩展 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 语句执行器还提供了一个返回分页结果的重载方法。