Neo4jClient

Spring Data Neo4j 附帶一個 Neo4j 客戶端,它在 Neo4j 的 Java 驅動程式之上提供了一個薄層。

雖然純 Java 驅動程式是一個非常通用的工具,除了命令式和響應式版本之外還提供非同步 API,但它不與 Spring 應用程式級事務整合。

SDN 儘可能直接地透過一種慣用客戶端的概念使用驅動程式。

該客戶端有以下主要目標

  1. 將 Spring 的事務管理整合到命令式和響應式場景中

  2. 必要時參與 JTA 事務

  3. 為命令式和響應式場景提供一致的 API

  4. 不增加任何對映開銷

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 中的大多數事物一樣,兩個客戶端都依賴於已配置的驅動程式例項。

建立命令式 Neo4j 客戶端例項
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 資料庫開啟響應式會話,並且在任何較低版本上都會失敗並丟擲異常。

建立響應式 Neo4j 客戶端例項
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);
    }
}
如果您啟用了事務,請確保為客戶端使用與為 Neo4jTransactionManagerReactiveNeo4jTransactionManager 提供例項時使用的相同驅動程式例項。如果您使用另一個驅動程式例項,客戶端將無法同步事務。

我們的 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 選擇要執行查詢的目標資料庫。

指定查詢

與客戶端的互動從查詢開始。查詢可以由純 StringSupplier<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();

在使用託管事務時直接與驅動程式互動

如果您不想要或不喜歡 Neo4jClientReactiveNeo4jClient 這種自以為是的“客戶端”方法,您可以讓客戶端將所有與資料庫的互動委託給您的程式碼。委託後的互動在客戶端的命令式和響應式版本中略有不同。

命令式版本接受 Function<StatementRunner, Optional<T>> 作為回撥。返回空 Optional 是可以的。

將資料庫互動委託給命令式 StatementRunner
Optional<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

將資料庫互動委託給響應式 RxStatementRunner
Mono<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 中,執行器的型別僅是為了向本手冊的讀者提供更清晰的說明。

© . This site is unofficial and not affiliated with VMware.