Spring Data Neo4j 扩展
Spring Data Neo4j 仓库的可用扩展
Spring Data Neo4j 提供了一些可以添加到仓库中的扩展或“mixin”。什么是 mixin?根据维基百科,mixin 是允许程序员将一些代码注入类的语言概念。Mixin 编程是一种软件开发风格,其中功能单元在类中创建,然后与其他类混合。
Java 在语言级别不支持该概念,但我们通过一些接口和运行时来模拟它,这些接口和运行时为其添加了适当的实现和拦截器。
默认添加的 Mixin 分别是 `QueryByExampleExecutor` 和 `ReactiveQueryByExampleExecutor`。这些接口在按示例查询中进行了详细解释。
提供的其他 mixin 为
-
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)
}
1 | 标准仓库声明 |
2 | Query DSL mixin |
或
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)
}
1 | 标准仓库声明 |
2 | Cypher DSL mixin |
使用 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");
1 | 定义一个名为 `Node` 的对象,目标是查询的根。 |
2 | 从中派生一些属性。 |
3 | 创建一个 `or` 条件。匿名参数用于名字,命名参数用于姓氏。这就是您在这些片段中定义参数的方式,也是它优于 Query-DSL mixin 的优势之一,后者无法做到这一点。文字可以用 `Cypher.literalOf` 来表示。 |
4 | 从其中一个属性定义一个 `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");
});
}
1 | 动态查询以类型安全的方式在辅助方法中构建 |
2 | 我们之前在这里已经看到过这一点,我们也在其中定义了一些保存模型的变量 |
3 | 我们定义一个匿名参数,由传递给方法的 `name` 的实际值填充。 |
4 | 辅助方法返回的语句用于查找实体 |
5 | 或投影。 |
`findAll` 方法的工作方式类似。命令式 Cypher-DSL 语句执行器还提供了一个返回分页结果的重载方法。