Spring Data Neo4j 扩展
Spring Data Neo4j 仓库的可用扩展
Spring Data Neo4j 提供了一些可以添加到仓库的扩展或“混入”(mixin)。什么是混入?根据 维基百科,混入是一种语言概念,允许程序员将一些代码注入到类中。混入编程是一种软件开发风格,其中功能单元在类中创建,然后与其他类混入。
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;
}
}
| 1 | 标准仓库声明 |
| 2 | Query DSL 混入 |
OR
interface PersonRepository extends Neo4jRepository<Person, Long>, (1)
CypherdslConditionExecutor<Person> {
(2)
}
| 1 | 标准仓库声明 |
| 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");
| 1 | 定义一个命名为 Node 的对象,指向查询的根 |
| 2 | 从中派生一些属性 |
| 3 | 创建一个 or 条件。第一个名字使用匿名参数,姓氏使用命名参数。这是你在此类片段中定义参数的方式,也是相对于 Query-DSL 混入的一个优势,Query-DSL 混入无法做到这一点。字面量可以用 Cypher.literalOf 来表示。 |
| 4 | 根据其中一个属性定义一个 SortItem |
Query-DSL 混入的代码看起来非常相似。选择 Query-DSL 混入的原因可能是对 API 的熟悉,并且它也适用于其他存储。反对它的原因是你需要在类路径上有一个额外的库,它缺少遍历关系的支持,以及上面提到的它不支持在谓词中使用参数的事实(技术上它支持,但没有 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");
});
}
| 1 | 动态查询以类型安全的方式在辅助方法中构建 |
| 2 | 我们已经在 此处 看过,在那里我们也定义了一些持有模型的变量 |
| 3 | 我们定义了一个匿名参数,由传递给方法的 name 的实际值填充 |
| 4 | 辅助方法返回的语句用于查找实体 |
| 5 | 或者一个投影。 |
findAll 方法的工作方式类似。命令式 Cypher-DSL 语句执行器还提供了返回分页结果的重载。