Spring Data Neo4j 擴充功能

Spring Data Neo4j 儲存庫的可用擴充功能

Spring Data Neo4j 提供了一些可以新增到儲存庫的擴充功能或「mixin」。什麼是 mixin?根據維基百科,mixin 是一種語言概念,允許程式設計師將一些程式碼注入到類別中。Mixin 程式設計是一種軟體開發風格,其中功能單元在類別中建立,然後與其他類別混合使用。

Java 在語言層級上不支援這個概念,但我們透過一些介面和執行階段來模擬它,以便為其新增適當的實作和攔截器。

預設新增的 Mixin 分別是 QueryByExampleExecutorReactiveQueryByExampleExecutor。這些介面在範例查詢中詳細說明。

提供的其他 mixin 為

  • QuerydslPredicateExecutor

  • CypherdslConditionExecutor

  • CypherdslStatementExecutor

  • ReactiveQuerydslPredicateExecutor

  • ReactiveCypherdslConditionExecutor

  • ReactiveCypherdslStatementExecutor

將動態條件新增至產生的查詢

QuerydslPredicateExecutorCypherdslConditionExecutor 都提供相同的概念: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 的優點之一,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 語句執行器也提供了傳回分頁結果的多載。