Spring Data Neo4j 扩展

Spring Data Neo4j Repository 的可用扩展

Spring Data Neo4j 提供了一些可以添加到 Repository 的扩展或“mixin”。什么是 mixin?根据 Wikipedia 的说法,mixin 是一种语言概念,它允许程序员将一些代码注入到类中。Mixin 编程是一种软件开发风格,其中功能单元在一个类中创建,然后与其他类混合。

Java 在语言层面不支持这个概念,但我们通过一些接口以及一个添加适当实现和拦截器的运行时来模拟它。

默认添加的 mixin 分别是 QueryByExampleExecutorReactiveQueryByExampleExecutor。这些接口在按示例查询中进行了详细解释。

提供的其他 mixin 有

  • QuerydslPredicateExecutor

  • CypherdslConditionExecutor

  • CypherdslStatementExecutor

  • ReactiveQuerydslPredicateExecutor

  • ReactiveCypherdslConditionExecutor

  • ReactiveCypherdslStatementExecutor

向生成的查询添加动态条件

QuerydslPredicateExecutorCypherdslConditionExecutor 都提供了相同的概念:SDN 生成一个查询,你提供要添加的“谓词”(Query DSL) 或“条件”(Cypher DSL)。我们推荐使用 Cypher DSL,因为这是 SDN 原生使用的。你甚至可能想要考虑使用注解处理器,它会为你生成一个静态元模型。

这是如何工作的?按照上述描述声明你的 Repository,并添加以下接口中的**一个**

interface QueryDSLPersonRepository extends
        Neo4jRepository<Person, Long>, (1)
        QuerydslPredicateExecutor<Person> { (2)
}
1 标准 Repository 声明
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 标准 Repository 声明
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 提供了 findOnefindAll 的多个重载。它们都将一个 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 语句执行器还提供了返回分页结果的重载。