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 语句执行器还提供了一个返回分页结果的重载方法。