Spring Data Neo4j 擴充套件
Spring Data Neo4j 倉庫的可用擴充套件
Spring Data Neo4j 提供了一些可以新增到倉庫的擴充套件或“混入”(mixin)。什麼是混入?根據 維基百科,混入是一種語言概念,允許程式設計師將一些程式碼注入到類中。混入程式設計是一種軟體開發風格,其中功能單元在類中建立,然後與其他類混入。
Java 在語言級別不支援這個概念,但我們透過一些介面和一個在執行時新增適當實現和攔截器的機制來模擬它。
預設新增的混入分別是 QueryByExampleExecutor 和 ReactiveQueryByExampleExecutor。這些介面在 按示例查詢 中有詳細解釋。
提供的其他混入有:
-
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)
}
static class DtoPersonProjection {
private final String firstName;
DtoPersonProjection(String firstName) {
this.firstName = firstName;
}
String getFirstName() {
return this.firstName;
}
}
| 1 | 標準倉庫宣告 |
| 2 | Query DSL 混入 |
OR
interface PersonRepository extends Neo4jRepository<Person, Long>, (1)
CypherdslConditionExecutor<Person> {
(2)
}
| 1 | 標準倉庫宣告 |
| 2 | Cypher DSL 混入 |
使用 Cypher DSL 條件執行器的示例用法
Node person = Cypher.node("Person").named("person"); (1)
Property firstName = person.property("firstName"); (2)
Property lastName = person.property("lastName");
assertThat(repository.findAll(
this.firstName.eq(Cypher.anonParameter("Helge"))
.or(this.lastName.eq(Cypher.parameter("someName", "B."))), (3)
this.lastName.descending() (4)
)).extracting(Person::getFirstName).containsExactly("Helge", "Bela");
| 1 | 定義一個命名為 Node 的物件,指向查詢的根 |
| 2 | 從中派生一些屬性 |
| 3 | 建立一個 or 條件。第一個名字使用匿名引數,姓氏使用命名引數。這是你在此類片段中定義引數的方式,也是相對於 Query-DSL 混入的一個優勢,Query-DSL 混入無法做到這一點。字面量可以用 Cypher.literalOf 來表示。 |
| 4 | 根據其中一個屬性定義一個 SortItem |
Query-DSL 混入的程式碼看起來非常相似。選擇 Query-DSL 混入的原因可能是對 API 的熟悉,並且它也適用於其他儲存。反對它的原因是你需要在類路徑上有一個額外的庫,它缺少遍歷關係的支援,以及上面提到的它不支援在謂詞中使用引數的事實(技術上它支援,但沒有 API 方法可以實際將它們傳遞給正在執行的查詢)。
為實體和投影使用(動態)Cypher-DSL 語句
新增相應的混入與使用 條件執行器 沒有什麼不同
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 語句執行器還提供了返回分頁結果的過載。