|
该版本仍在开发中,尚未被视为稳定。对于最新稳定版本,请使用Spring Data Neo4j 8.0.0! |
Neo4jClient
Spring Data Neo4j 自带一个 Neo4j 客户端,在 Neo4j 的 Java 驱动之上提供一层薄层。
虽然普通的 Java 驱动是一个非常多功能的工具,除了命令式和响应式版本外,还提供异步 API,但它无法与 Spring 应用级事务集成。
SDN通过习用客户端的概念尽可能直接地使用驱动程序。
客户的主要目标如下
-
集成到 Springs 事务管理中,适用于紧急和被动场景
-
如有需要,参与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()
命令式版本此刻将与数据库交互,获取请求的结果或摘要,并以可选<>或者收集.
而响应式版本则会返回请求类型的发布者。与数据库交互和结果检索只有在订阅发布者后才会发生。发布者只能订阅一次。
获取客户端实例
和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://localhost: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://localhost:7687", AuthTokens.basic("neo4j", "secret"));
ReactiveNeo4jClient client = ReactiveNeo4jClient.create(driver);
}
}
确保你为客户端使用与提供Neo4jTransactionManager或ReactiveNeo4jTransactionManager如果你启用了交易。如果你使用其他驱动程序实例,客户端将无法同步交易。 |
我们的 Spring Boot Starters提供了一个适合环境(命令式或响应式)的 Neo4j 客户端的现成可用版本,通常你不需要自己配置实例。
用法
选择目标数据库
Neo4j 客户端已做好充分准备,适合配合 Neo4j 4.0 的多数据库功能使用。除非您另有指定,否则客户端使用默认数据库。客户端的 Fluent API 允许在查询声明执行后指定一次目标数据库。选择目标数据库时,可以通过响应式客户端演示:
Flux<Map<String, Object>> allActors = client
.query("MATCH (p:Person) RETURN p")
.in("neo4j") (1)
.fetch()
.all();
| 1 | 选择查询将要执行的目标数据库。 |
指定查询
与客户端的交互始于查询。查询可以通过一个平面定义字符串或者提供商<弦>. 提供商的评估会尽可能晚,任何查询生成器都可以提供。
Mono<Map<String, Object>> firstActor = client
.query(() -> "MATCH (p:Person) RETURN p")
.fetch()
.first();
检索结果
正如前面的列表所示,与客户的互动总是以通话结束,获取以及将收到多少结果。
既有被动的,也有紧迫性的客户报价
一()-
查询结果恰好只有一个
first()-
期待结果并返回首个记录
all()-
检索所有返回的记录
命令式客户端返回可选<T>和收藏书<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 客户端则方便地将数值绑定到它们上。
| 客户端不会检查所有参数是否都绑定,或者值是否过多。 这就交给Drivers了。 然而,客户端阻止你重复使用参数名。 |
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。
你可以提供函数 <T,映射<字符串,对象>>对于任意给定的域对象,比如 Example 中的自行车所有者,将域类型映射到 Neo4j 客户端,将这些域对象映射到Drivers能理解的参数。
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 | 这跟方法允许指定结合器函数。 |
处理结果对象
两个客户都退回地图的合集或出版商(Map<String,对象>).
这些映射与查询可能产生的记录完全对应。
此外,你还可以插上自己的电源双功能<类型系统,记录,T>通过fetchAs用来复制你的域对象。
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();
类型系统允许访问底层Java驱动用来填充记录的类型。
使用领域感知映射函数
如果你知道查询结果中会包含在你的应用中具有实体定义的节点,
你可以用注射剂映射上下文获取它们的映射函数并在映射过程中应用。
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<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 | 可选选择目标数据库。 |
注意,在两者中将数据库交互委托给命令式声明跑者和将数据库交互委托给被动式RxStatementRunner跑者类型仅为本手册读者提供更清晰的说明。