Neo4jClient
Spring Data Neo4j 附帶一個 Neo4j 客戶端,它在 Neo4j 的 Java 驅動程式之上提供了一個薄層。
雖然純 Java 驅動程式是一個非常通用的工具,除了命令式和響應式版本之外還提供非同步 API,但它不與 Spring 應用程式級事務整合。
SDN 儘可能直接地透過一種慣用客戶端的概念使用驅動程式。
該客戶端有以下主要目標
-
將 Spring 的事務管理整合到命令式和響應式場景中
-
必要時參與 JTA 事務
-
為命令式和響應式場景提供一致的 API
-
不增加任何對映開銷
SDN 依賴所有這些特性並使用它們來實現其實體對映特性。
請檢視SDN 構建塊,瞭解命令式和響應式 Neo4 客戶端在我們的堆疊中的位置。
Neo4j 客戶端有兩種風格
-
org.springframework.data.neo4j.core.Neo4jClient -
org.springframework.data.neo4j.core.ReactiveNeo4jClient
雖然兩個版本都提供了使用相同詞彙和語法的 API,但它們不是 API 相容的。兩個版本都具有相同的流暢 API 來指定查詢、繫結引數和提取結果。
命令式還是響應式?
與 Neo4j 客戶端的互動通常以呼叫以下方法結束:
-
fetch().one() -
fetch().first() -
fetch().all() -
run()
命令式版本將在此時與資料庫互動並獲取請求的結果或摘要,並將其包裝在 Optional<> 或 Collection 中。
相比之下,響應式版本將返回請求型別的釋出者。與資料庫的互動和結果的檢索在釋出者被訂閱之前不會發生。釋出者只能被訂閱一次。
獲取客戶端例項
與 SDN 中的大多數事物一樣,兩個客戶端都依賴於已配置的驅動程式例項。
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.springframework.data.neo4j.core.Neo4jClient;
public class Demo {
public static void main(String...args) {
Driver driver = GraphDatabase
.driver("neo4j://:7687", AuthTokens.basic("neo4j", "secret"));
Neo4jClient client = Neo4jClient.create(driver);
}
}
驅動程式只能針對 4.0 資料庫開啟響應式會話,並且在任何較低版本上都會失敗並丟擲異常。
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.springframework.data.neo4j.core.ReactiveNeo4jClient;
public class Demo {
public static void main(String...args) {
Driver driver = GraphDatabase
.driver("neo4j://:7687", AuthTokens.basic("neo4j", "secret"));
ReactiveNeo4jClient client = ReactiveNeo4jClient.create(driver);
}
}
如果您啟用了事務,請確保為客戶端使用與為 Neo4jTransactionManager 或 ReactiveNeo4jTransactionManager 提供例項時使用的相同驅動程式例項。如果您使用另一個驅動程式例項,客戶端將無法同步事務。 |
我們的 Spring Boot Starter 提供了一個可即用的 Neo4j 客戶端 bean,它適合環境(命令式或響應式),您通常無需配置自己的例項。
用法
選擇目標資料庫
Neo4j 客戶端已充分準備好與 Neo4j 4.0 的多資料庫特性一起使用。除非您另有指定,否則客戶端使用預設資料庫。客戶端的流暢 API 允許在宣告要執行的查詢之後只指定一次目標資料庫。選擇目標資料庫使用響應式客戶端演示了這一點
Flux<Map<String, Object>> allActors = client
.query("MATCH (p:Person) RETURN p")
.in("neo4j") (1)
.fetch()
.all();
| 1 | 選擇要執行查詢的目標資料庫。 |
指定查詢
與客戶端的互動從查詢開始。查詢可以由純 String 或 Supplier<String> 定義。供應商將盡可能晚地進行評估,並且可以由任何查詢構建器提供。
Mono<Map<String, Object>> firstActor = client
.query(() -> "MATCH (p:Person) RETURN p")
.fetch()
.first();
檢索結果
如前面的列表中所示,與客戶端的互動總是以呼叫 fetch 以及應接收多少結果而結束。響應式和命令式客戶端都提供
one()-
預期查詢只返回一個結果
first()-
預期有結果並返回第一條記錄
all()-
檢索所有返回的記錄
命令式客戶端分別返回 Optional<T> 和 Collection<T>,而響應式客戶端返回 Mono<T> 和 Flux<T>,後者只有在訂閱後才會執行。
如果您不期望查詢有任何結果,那麼在指定查詢後使用 run()。
Mono<ResultSummary> summary = reactiveClient
.query("MATCH (m:Movie) where m.title = 'Aeon Flux' DETACH DELETE m")
.run();
summary
.map(ResultSummary::counters)
.subscribe(counters ->
System.out.println(counters.nodesDeleted() + " nodes have been deleted")
); (1)
| 1 | 實際查詢在此處透過訂閱釋出者觸發。 |
請花點時間比較這兩個列表並理解實際查詢何時觸發的差異。
ResultSummary resultSummary = imperativeClient
.query("MATCH (m:Movie) where m.title = 'Aeon Flux' DETACH DELETE m")
.run(); (1)
SummaryCounters counters = resultSummary.counters();
System.out.println(counters.nodesDeleted() + " nodes have been deleted")
| 1 | 在這裡,查詢立即觸發。 |
對映引數
查詢可以包含命名引數($someName),Neo4j 客戶端可以輕鬆地將值繫結到它們。
| 客戶端不檢查所有引數是否都已繫結,或者是否存在過多的值。這留給驅動程式處理。但是,客戶端會阻止您兩次使用相同的引數名稱。 |
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", "Li.*");
Flux<Map<String, Object>> directorAndMovies = client
.query(
"MATCH (p:Person) - [:DIRECTED] -> (m:Movie {title: $title}), (p) - [:WROTE] -> (om:Movie) " +
"WHERE p.name =~ $name " +
" AND p.born < $someDate.year " +
"RETURN p, om"
)
.bind("The Matrix").to("title") (1)
.bind(LocalDate.of(1979, 9, 21)).to("someDate")
.bindAll(parameters) (2)
.fetch()
.all();
| 1 | 有一個用於繫結簡單型別的流暢 API。 |
| 2 | 或者,引數可以透過命名引數的對映進行繫結。 |
SDN 執行許多複雜的對映,它使用與您可以從客戶端使用的相同的 API。
您可以為任何給定的領域物件(例如領域型別示例中的腳踏車所有者)提供 Function<T, Map<String, Object>> 給 Neo4j 客戶端,以將這些領域物件對映為驅動程式可以理解的引數。
public class Director {
private final String name;
private final List<Movie> movies;
Director(String name, List<Movie> movies) {
this.name = name;
this.movies = new ArrayList<>(movies);
}
public String getName() {
return name;
}
public List<Movie> getMovies() {
return Collections.unmodifiableList(movies);
}
}
public class Movie {
private final String title;
public Movie(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
}
對映函式必須填充查詢中可能出現的所有命名引數,如使用對映函式繫結領域物件所示
Director joseph = new Director("Joseph Kosinski",
Arrays.asList(new Movie("Tron Legacy"), new Movie("Top Gun: Maverick")));
Mono<ResultSummary> summary = client
.query(""
+ "MERGE (p:Person {name: $name}) "
+ "WITH p UNWIND $movies as movie "
+ "MERGE (m:Movie {title: movie}) "
+ "MERGE (p) - [o:DIRECTED] -> (m) "
)
.bind(joseph).with(director -> { (1)
Map<String, Object> mappedValues = new HashMap<>();
List<String> movies = director.getMovies().stream()
.map(Movie::getTitle).collect(Collectors.toList());
mappedValues.put("name", director.getName());
mappedValues.put("movies", movies);
return mappedValues;
})
.run();
| 1 | with 方法允許指定繫結器函式。 |
使用結果物件
兩個客戶端都返回對映(Map<String, Object>)的集合或釋出者。這些對映與查詢可能產生的記錄完全對應。
此外,您可以透過 fetchAs 插入您自己的 BiFunction<TypeSystem, Record, T> 來重現您的領域物件。
Mono<Director> lily = client
.query(""
+ " MATCH (p:Person {name: $name}) - [:DIRECTED] -> (m:Movie)"
+ "RETURN p, collect(m) as movies")
.bind("Lilly Wachowski").to("name")
.fetchAs(Director.class).mappedBy((TypeSystem t, Record record) -> {
List<Movie> movies = record.get("movies")
.asList(v -> new Movie((v.get("title").asString())));
return new Director(record.get("name").asString(), movies);
})
.one();
TypeSystem 允許訪問底層 Java 驅動程式用於填充記錄的型別。
使用領域感知對映函式
如果您知道查詢結果將包含在您的應用程式中具有實體定義的節點,您可以使用可注入的 MappingContext 來檢索它們的對映函式並在對映期間應用它們。
BiFunction<TypeSystem, MapAccessor, Movie> mappingFunction = neo4jMappingContext.getRequiredMappingFunctionFor(Movie.class);
Mono<Director> lily = client
.query(""
+ " MATCH (p:Person {name: $name}) - [:DIRECTED] -> (m:Movie)"
+ "RETURN p, collect(m) as movies")
.bind("Lilly Wachowski").to("name")
.fetchAs(Director.class).mappedBy((TypeSystem t, Record record) -> {
List<Movie> movies = record.get("movies")
.asList(movie -> mappingFunction.apply(t, movie));
return new Director(record.get("name").asString(), movies);
})
.one();
在使用託管事務時直接與驅動程式互動
如果您不想要或不喜歡 Neo4jClient 或 ReactiveNeo4jClient 這種自以為是的“客戶端”方法,您可以讓客戶端將所有與資料庫的互動委託給您的程式碼。委託後的互動在客戶端的命令式和響應式版本中略有不同。
命令式版本接受 Function<StatementRunner, Optional<T>> 作為回撥。返回空 Optional 是可以的。
StatementRunnerOptional<Long> result = client
.delegateTo((StatementRunner runner) -> {
// Do as many interactions as you want
long numberOfNodes = runner.run("MATCH (n) RETURN count(n) as cnt")
.single().get("cnt").asLong();
return Optional.of(numberOfNodes);
})
// .in("aDatabase") (1)
.run();
| 1 | 選擇目標資料庫中所述的資料庫選擇是可選的。 |
響應式版本接收 RxStatementRunner。
RxStatementRunnerMono<Integer> result = client
.delegateTo((RxStatementRunner runner) ->
Mono.from(runner.run("MATCH (n:Unused) DELETE n").summary())
.map(ResultSummary::counters)
.map(SummaryCounters::nodesDeleted))
// .in("aDatabase") (1)
.run();
| 1 | 可選的目標資料庫選擇。 |
請注意,在將資料庫互動委託給命令式 StatementRunner 和將資料庫互動委託給響應式 RxStatementRunner 中,執行器的型別僅是為了向本手冊的讀者提供更清晰的說明。