此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Data Neo4j 7.4.4!spring-doc.cadn.net.cn

Spring Data Neo4j 扩展

Spring Data Neo4j 存储库的可用扩展

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

Java 在语言层面上不支持这个概念,但我们确实通过几个接口和一个运行时来模拟它 这为 .spring-doc.cadn.net.cn

默认情况下添加的 Mixin 是QueryByExampleExecutorReactiveQueryByExampleExecutor分别。这些接口是 在 Query by Example 中有详细说明。spring-doc.cadn.net.cn

提供的其他 mixin 包括:spring-doc.cadn.net.cn

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

QuerydslPredicateExecutorCypherdslConditionExecutor提供相同的概念:SDN 生成查询,您 提供将添加的 “predicates” (Query DSL) 或 “conditions” (Cypher DSL)。我们推荐使用 Cypher DSL,因为它是 SDN 原生使用的内容。您甚至可能要考虑使用生成 为您提供一个静态元模型。spring-doc.cadn.net.cn

这是如何运作的?如上所述声明您的存储库,并添加以下接口之一:spring-doc.cadn.net.cn

interface QueryDSLPersonRepository extends
        Neo4jRepository<Person, Long>, (1)
        QuerydslPredicateExecutor<Person> { (2)
}
1 标准存储库声明
2 查询 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)
    }
1 Standard repository declaration
2 The Cypher DSL mixin

Exemplary usage is shown with the Cypher DSL condition executor:spring-doc.cadn.net.cn

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 Define a named Node object, targeting the root of the query
2 Derive some properties from it
3 Create an or condition. An anonymous parameter is used for the first name, a named parameter for the last name. This is how you define parameters in those fragments and one of the advantages over the Query-DSL mixin which can’t do that. Literals can be expressed with Cypher.literalOf.
4 Define a SortItem from one of the properties

The code looks pretty similar for the Query-DSL mixin. Reasons for the Query-DSL mixin can be familiarity of the API and that it works with other stores, too. Reasons against it are the fact that you need an additional library on the class path, it’s missing support for traversing relationships and the above-mentioned fact that it doesn’t support parameters in its predicates (it technically does, but there are no API methods to actually pass them to the query being executed).spring-doc.cadn.net.cn

Using (dynamic) Cypher-DSL statements for entities and projections

Adding the corresponding mixin is not different from using the condition excecutor:spring-doc.cadn.net.cn

interface PersonRepository extends
        Neo4jRepository<Person, Long>,
        CypherdslStatementExecutor<Person> {
}

Please use the ReactiveCypherdslStatementExecutor when extending the ReactiveNeo4jRepository.spring-doc.cadn.net.cn

The CypherdslStatementExecutor comes with several overloads for findOne and findAll. They all take a Cypher-DSL statement respectively an ongoing definition of that as a first parameter and in case of the projecting methods, a type.spring-doc.cadn.net.cn

If a query requires parameters, they must be defined via the Cypher-DSL itself and also populated by it, as the following listing shows:spring-doc.cadn.net.cn

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 The dynamic query is build in a type safe way in a helper method
2 We already saw this in here, where we also defined some variables holding the model
3 We define an anonymous parameter, filled by the actual value of name, which was passed to the method
4 The statement returned from the helper method is used to find an entity
5 Or a projection.

The findAll methods works similar. The imperative Cypher-DSL statement executor also provides an overload returning paged results.spring-doc.cadn.net.cn