neo4j-driver-java-skill
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWhen to Use
适用场景
- Java/Kotlin code connecting to Neo4j (Aura or self-managed)
- Setting up driver, sessions, transactions in Maven/Gradle projects
- Debugging result handling, error recovery, connection pool issues
- Async () or reactive (Project Reactor / RxJava) Neo4j access
CompletableFuture
- 使用Java/Kotlin代码连接Neo4j(Aura或自托管版本)
- 在Maven/Gradle项目中配置驱动、会话和事务
- 调试结果处理、错误恢复和连接池问题
- 异步()或响应式(Project Reactor / RxJava)方式访问Neo4j
CompletableFuture
When NOT to Use
不适用场景
- Cypher query authoring/optimization →
neo4j-cypher-skill - Driver version upgrades →
neo4j-migration-skill - Spring Data Neo4j (,
@Node,@Relationship) →Neo4jRepositoryneo4j-spring-data-skill
- Cypher查询编写/优化 → 使用
neo4j-cypher-skill - 驱动版本升级 → 使用
neo4j-migration-skill - Spring Data Neo4j(、
@Node、@Relationship)→ 使用Neo4jRepositoryneo4j-spring-data-skill
Dependency
依赖配置
Maven
Maven
xml
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>6.0.5</version>
</dependency>xml
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>6.0.5</version>
</dependency>Gradle
Gradle
groovy
implementation 'org.neo4j.driver:neo4j-java-driver:6.0.5'groovy
implementation 'org.neo4j.driver:neo4j-java-driver:6.0.5'Environment Variables
环境变量
Standard pattern for connection config — never hardcode credentials:
java
String uri = System.getenv().getOrDefault("NEO4J_URI", "neo4j://localhost:7687");
String user = System.getenv().getOrDefault("NEO4J_USERNAME", "neo4j");
String password = System.getenv().getOrDefault("NEO4J_PASSWORD", "");
String database = System.getenv().getOrDefault("NEO4J_DATABASE", "neo4j");Spring Boot: inject via or :
@Value("${spring.neo4j.uri}")application.propertiesproperties
spring.neo4j.uri=neo4j+s://xxx.databases.neo4j.io
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret连接配置的标准模式 — 切勿硬编码凭证:
java
String uri = System.getenv().getOrDefault("NEO4J_URI", "neo4j://localhost:7687");
String user = System.getenv().getOrDefault("NEO4J_USERNAME", "neo4j");
String password = System.getenv().getOrDefault("NEO4J_PASSWORD", "");
String database = System.getenv().getOrDefault("NEO4J_DATABASE", "neo4j");Spring Boot:通过或注入:
@Value("${spring.neo4j.uri}")application.propertiesproperties
spring.neo4j.uri=neo4j+s://xxx.databases.neo4j.io
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secretDriver Lifecycle
驱动生命周期
One per application — thread-safe, expensive to create. Implement or use try-with-resources.
DriverAutoCloseablejava
// Long-lived singleton
var driver = GraphDatabase.driver(
"neo4j+s://xxx.databases.neo4j.io", // Aura TLS+routing
AuthTokens.basic(user, password));
driver.verifyConnectivity(); // fail fast
// Short-lived (tests / CLI)
try (var driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password))) {
driver.verifyConnectivity();
// ...
}URI schemes:
| URI | Use |
|---|---|
| Unencrypted, cluster routing |
| TLS + cluster routing (Aura) |
| Unencrypted, single instance |
| TLS, single instance |
Auth options: · · ·
AuthTokens.basic(u,p)AuthTokens.bearer(token)AuthTokens.kerberos(b64)AuthTokens.none()每个应用仅需一个实例 — 线程安全,创建成本高。实现接口或使用try-with-resources语法。
DriverAutoCloseablejava
// 长期存活的单例实例
var driver = GraphDatabase.driver(
"neo4j+s://xxx.databases.neo4j.io", // Aura TLS+路由
AuthTokens.basic(user, password));
driver.verifyConnectivity(); // 快速失败校验
// 短期使用(测试/命令行工具)
try (var driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password))) {
driver.verifyConnectivity();
// ...
}URI协议:
| URI | 用途 |
|---|---|
| 未加密,集群路由 |
| TLS + 集群路由(Aura) |
| 未加密,单实例 |
| TLS,单实例 |
认证选项: · · ·
AuthTokens.basic(u,p)AuthTokens.bearer(token)AuthTokens.kerberos(b64)AuthTokens.none()Choosing the Right API
选择合适的API
| API | When | Auto-retry | Streaming |
|---|---|---|---|
| Default for most queries | ✅ | ❌ eager |
| Large results, callback control | ✅ | ✅ |
| Multi-method, external coordination | ❌ | ✅ |
| Self-managing queries ( | ❌ | ✅ |
| Non-blocking | ✅ | ✅ |
| Reactor/RxJava backpressure | ✅ | ✅ |
CALL { … } IN TRANSACTIONSUSING PERIODIC COMMITsession.run()executableQueryexecuteRead/Write| API | 适用场景 | 自动重试 | 流式处理 |
|---|---|---|---|
| 大多数查询的默认选择 | ✅ | ❌ 立即加载 |
| 大结果集、回调控制 | ✅ | ✅ |
| 跨方法操作、外部协调 | ❌ | ✅ |
| 自我管理的查询( | ❌ | ✅ |
| 非阻塞 | ✅ | ✅ |
| Reactor/RxJava背压处理 | ✅ | ✅ |
CALL { … } IN TRANSACTIONSUSING PERIODIC COMMITsession.run()executableQueryexecuteRead/WriteexecutableQuery
— Default
executableQueryexecutableQuery
— 默认推荐
executableQueryjava
// Read — route to replicas
var result = driver.executableQuery("""
MATCH (p:Person {name: $name})-[:KNOWS]->(friend)
RETURN friend.name AS name
""")
.withParameters(Map.of("name", "Alice"))
.withConfig(QueryConfig.builder()
.withDatabase("neo4j") // always specify — avoids home-db round-trip
.withRouting(RoutingControl.READ)
.build())
.execute();
result.records().forEach(r -> System.out.println(r.get("name").asString()));
long ms = result.summary().resultAvailableAfter(TimeUnit.MILLISECONDS);
// Write
driver.executableQuery("CREATE (p:Person {name: $name, age: $age})")
.withParameters(Map.of("name", "Bob", "age", 30))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute();Never string-interpolate Cypher. Always .
.withParameters(Map.of(...))java
// 读操作 — 路由到副本节点
var result = driver.executableQuery("""
MATCH (p:Person {name: $name})-[:KNOWS]->(friend)
RETURN friend.name AS name
""")
.withParameters(Map.of("name", "Alice"))
.withConfig(QueryConfig.builder()
.withDatabase("neo4j") // 始终指定 — 避免主数据库往返
.withRouting(RoutingControl.READ)
.build())
.execute();
result.records().forEach(r -> System.out.println(r.get("name").asString()));
long ms = result.summary().resultAvailableAfter(TimeUnit.MILLISECONDS);
// 写操作
driver.executableQuery("CREATE (p:Person {name: $name, age: $age})")
.withParameters(Map.of("name", "Bob", "age", 30))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute();切勿拼接Cypher字符串。始终使用传递参数。
.withParameters(Map.of(...))Managed Transactions (executeRead
/ executeWrite
)
executeReadexecuteWrite托管事务(executeRead
/ executeWrite
)
executeReadexecuteWriteSessions are NOT thread-safe — one per request/thread, always close.
java
try (var session = driver.session(SessionConfig.builder()
.withDatabase("neo4j").build())) {
// Read → replica routing
var names = session.executeRead(tx -> {
var result = tx.run(
"MATCH (p:Person) WHERE p.name STARTS WITH $prefix RETURN p.name AS name",
Map.of("prefix", "Al"));
return result.stream().map(r -> r.get("name").asString()).toList(); // collect INSIDE
});
// Write → leader routing
session.executeWriteWithoutResult(tx ->
tx.run("CREATE (p:Person {name: $name})", Map.of("name", "Carol"))
);
}Session不是线程安全的 — 每个请求/线程一个Session,使用后务必关闭。
java
try (var session = driver.session(SessionConfig.builder()
.withDatabase("neo4j").build())) {
// 读操作 → 路由到副本节点
var names = session.executeRead(tx -> {
var result = tx.run(
"MATCH (p:Person) WHERE p.name STARTS WITH $prefix RETURN p.name AS name",
Map.of("prefix", "Al"));
return result.stream().map(r -> r.get("name").asString()).toList(); // 在回调内收集结果
});
// 写操作 → 路由到主节点
session.executeWriteWithoutResult(tx ->
tx.run("CREATE (p:Person {name: $name})", Map.of("name", "Carol"))
);
}Result must be consumed INSIDE the callback
结果必须在回调内消费
ResultResultConsumedExceptionjava
// ❌ Returns Result — already closed by the time caller uses it
var result = session.executeRead(tx ->
tx.run("MATCH (p:Person) RETURN p.name AS name"));
result.stream().forEach(...); // throws ResultConsumedException
// ✅ Collect to List inside callback
var names = session.executeRead(tx ->
tx.run("MATCH (p:Person) RETURN p.name AS name")
.stream().map(r -> r.get("name").asString()).toList());ResultResultConsumedExceptionjava
// ❌ 返回Result — 调用方使用时事务已关闭
var result = session.executeRead(tx ->
tx.run("MATCH (p:Person) RETURN p.name AS name"));
result.stream().forEach(...); // 抛出ResultConsumedException
// ✅ 在回调内收集到List
var names = session.executeRead(tx ->
tx.run("MATCH (p:Person) RETURN p.name AS name")
.stream().map(r -> r.get("name").asString()).toList());Callback rules
回调规则
- Consume each before next
Result— multiple open cursors = undefined behaviour.tx.run() - No side effects (HTTP, email, metric increments) — callback may be retried on transient errors.
- Use (idempotent), not
MERGE, for retry-safe writes.CREATE - → replica;
executeRead→ leader.executeWrite
- 在执行下一个前消费每个
tx.run()— 多个打开的游标会导致未定义行为。Result - 避免副作用(HTTP请求、邮件发送、指标统计)— 回调可能因临时错误重试。
- 写操作使用(幂等)而非
MERGE,确保重试安全。CREATE - → 路由到副本;
executeRead→ 路由到主节点。executeWrite
TransactionConfig — timeouts & metadata
TransactionConfig — 超时与元数据
java
var config = TransactionConfig.builder()
.withTimeout(Duration.ofSeconds(5))
.withMetadata(Map.of("app", "myService", "user", userId)) // visible in SHOW TRANSACTIONS
.build();
session.executeRead(tx -> { /* ... */ }, config);java
var config = TransactionConfig.builder()
.withTimeout(Duration.ofSeconds(5))
.withMetadata(Map.of("app", "myService", "user", userId)) // 在SHOW TRANSACTIONS中可见
.build();
session.executeRead(tx -> { /* ... */ }, config);Explicit Transactions
显式事务
Use when work spans multiple methods or requires external coordination. Not auto-retried.
java
try (var session = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
var tx = session.beginTransaction();
try {
doPartA(tx);
doPartB(tx);
tx.commit();
} catch (Exception e) {
try { tx.rollback(); } catch (Exception rb) { e.addSuppressed(rb); }
throw e;
}
}tx.rollback()addSuppressedCommit uncertainty: if throws , the commit may or may not have succeeded. Design writes as idempotent ( + unique constraints) so retrying is safe.
tx.commit()ServiceUnavailableExceptionMERGEChoose explicit vs managed:
- Auto-retry needed → /
executeReadexecuteWrite - Work spans multiple methods → explicit (pass as parameter)
tx - Coordinating with external I/O → explicit (commit only after I/O succeeds)
适用于跨多个方法的操作或需要外部协调的场景。不支持自动重试。
java
try (var session = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
var tx = session.beginTransaction();
try {
doPartA(tx);
doPartB(tx);
tx.commit();
} catch (Exception e) {
try { tx.rollback(); } catch (Exception rb) { e.addSuppressed(rb); }
throw e;
}
}tx.rollback()addSuppressed提交不确定性:如果抛出,提交可能成功也可能失败。将写操作设计为幂等( + 唯一约束),确保重试安全。
tx.commit()ServiceUnavailableExceptionMERGE选择显式事务还是托管事务:
- 需要自动重试 → 使用/
executeReadexecuteWrite - 操作跨多个方法 → 使用显式事务(将作为参数传递)
tx - 与外部I/O协调 → 使用显式事务(仅在I/O成功后提交)
Error Handling
错误处理
java
try {
driver.executableQuery("...").execute();
} catch (ServiceUnavailableException e) {
// No servers — check connection
} catch (SessionExpiredException e) {
// Server closed session — open new one
} catch (TransientException e) {
// Managed txns retry automatically; explicit txns need manual retry
} catch (Neo4jException e) {
// Cypher/constraint error — e.code() gives GQL status code
}Managed transactions auto-retry — no catch needed.
TransientExceptionjava
try {
driver.executableQuery("...").execute();
} catch (ServiceUnavailableException e) {
// 无可用服务器 — 检查连接
} catch (SessionExpiredException e) {
// 服务器关闭会话 — 开启新会话
} catch (TransientException e) {
// 托管事务会自动重试;显式事务需手动重试
} catch (Neo4jException e) {
// Cypher/约束错误 — e.code()返回GQL状态码
}托管事务会自动重试 — 无需捕获。
TransientExceptionData Types & Value Extraction
数据类型与值提取
| Cypher type | Java accessor |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
java
var record = result.records().get(0);
String name = record.get("name").asString();
long age = record.get("age").asLong();
var node = record.get("p").asNode();
String label = node.labels().iterator().next();
Map<String,Object> props = node.asMap();| Cypher类型 | Java访问方法 |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
java
var record = result.records().get(0);
String name = record.get("name").asString();
long age = record.get("age").asLong();
var node = record.get("p").asNode();
String label = node.labels().iterator().next();
Map<String,Object> props = node.asMap();Null safety — two distinct cases
Null安全性 — 两种不同情况
| Situation | | |
|---|---|---|
| Key present, value non-null | the value | returns string |
| Key present, value is graph null | | throws |
| Key absent (typo / not projected) | | throws |
java
// Graph null — use default overload (safe only if key is always projected):
String city = record.get("city").asString("Unknown");
// Absent key — check containsKey first:
if (record.containsKey("city") && !record.get("city").isNull()) {
String city = record.get("city").asString();
}| 场景 | | |
|---|---|---|
| 键存在,值非null | 返回对应值 | 返回字符串 |
| 键存在,值为图数据库null | 返回 | 抛出 |
| 键不存在(拼写错误/未投影) | 返回 | 抛出 |
java
// 图数据库null — 使用默认值重载(仅在键始终被投影时安全):
String city = record.get("city").asString("Unknown");
// 键不存在 — 先检查containsKey:
if (record.containsKey("city") && !record.get("city").isNull()) {
String city = record.get("city").asString();
}Object Mapping
对象映射
Map query results to Java records/classes directly — eliminates manual accessor calls.
java
// Domain record — field names match RETURN aliases (case-sensitive)
public record Person(String name, long age) {}
// Map single record
var person = driver.executableQuery("MATCH (p:Person {name: $name}) RETURN p.name AS name, p.age AS age")
.withParameters(Map.of("name", "Alice"))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute()
.records()
.stream()
.map(r -> r.get("name").asString()) // or: r.as(Person.class) — see note
.findFirst()
.orElseThrow();
// Using .as(Person.class) — maps RETURN keys to record fields by name
var person2 = driver.executableQuery("""
MATCH (p:Person {name: $name})
RETURN p.name AS name, p.age AS age
""")
.withParameters(Map.of("name", "Tom Hanks"))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute()
.records()
.stream()
.map(record -> record.get("p").as(Person.class))
.findFirst()
.orElseThrow(() -> new RuntimeException("Person not found"));Nested mapping — return a map projection and include for lists:
COLLECT {}java
public record Movie(String title, List<Person> actors) {}
var movieCypher = """
MATCH (movie:Movie)
LIMIT 1
RETURN movie {
.title,
actors: COLLECT {
MATCH (actor:Person)-[:ACTED_IN]->(movie)
RETURN actor
}
}
""";
var movie = driver.executableQuery(movieCypher)
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute()
.records()
.stream()
.map(r -> r.get("movie").as(Movie.class))
.findFirst()
.orElseThrow();Only mapped properties defined in the record are populated — extra properties returned by Cypher are ignored.
直接将查询结果映射到Java记录/类 — 消除手动调用访问方法的操作。
java
// 领域记录 — 字段名与RETURN别名匹配(区分大小写)
public record Person(String name, long age) {}
// 映射单个记录
var person = driver.executableQuery("MATCH (p:Person {name: $name}) RETURN p.name AS name, p.age AS age")
.withParameters(Map.of("name", "Alice"))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute()
.records()
.stream()
.map(r -> r.get("name").asString()) // 或:r.as(Person.class) — 见注释
.findFirst()
.orElseThrow();
// 使用.as(Person.class) — 将RETURN键按名称映射到记录字段
var person2 = driver.executableQuery("""
MATCH (p:Person {name: $name})
RETURN p.name AS name, p.age AS age
""")
.withParameters(Map.of("name", "Tom Hanks"))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute()
.records()
.stream()
.map(record -> record.get("p").as(Person.class))
.findFirst()
.orElseThrow(() -> new RuntimeException("Person not found"));嵌套映射 — 返回map投影并使用处理列表:
COLLECT {}java
public record Movie(String title, List<Person> actors) {}
var movieCypher = """
MATCH (movie:Movie)
LIMIT 1
RETURN movie {
.title,
actors: COLLECT {
MATCH (actor:Person)-[:ACTED_IN]->(movie)
RETURN actor
}
}
""";
var movie = driver.executableQuery(movieCypher)
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute()
.records()
.stream()
.map(r -> r.get("movie").as(Movie.class))
.findFirst()
.orElseThrow();仅填充记录中定义的映射属性 — Cypher返回的额外属性会被忽略。
Performance Patterns
性能优化模式
Always specify database — omitting triggers home-db round-trip on every call.
Route reads to replicas — in or use .
RoutingControl.READQueryConfigexecuteReadBatch writes with — pass (plain maps only; custom objects fail):
UNWINDList<Map<String,Object>>java
List<Map<String, Object>> rows = people.stream()
.map(p -> Map.<String, Object>of("name", p.name(), "age", p.age()))
.toList();
driver.executableQuery("UNWIND $items AS item MERGE (p:Person {name: item.name}) SET p.age = item.age")
.withParameters(Map.of("items", rows))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute();Allowed leaf types in parameter maps: , ///, /, , , , . Custom objects and must be converted first.
StringLongIntegerShortByteDoubleFloatBooleanList<?>Map<String,?>nullLocalDateGroup writes in one transaction — one with a loop, not one per iteration.
executeWriteexecuteWriteConnection pool — default 100 connections. Tune if exhausted:
java
Config.builder()
.withMaxConnectionPoolSize(50)
.withConnectionAcquisitionTimeout(30, TimeUnit.SECONDS)
.build()始终指定数据库 — 省略会导致每次调用都触发主数据库往返。
将读操作路由到副本节点 — 在中设置或使用。
QueryConfigRoutingControl.READexecuteRead使用批量写入 — 传递(仅支持普通map;自定义对象会失败):
UNWINDList<Map<String,Object>>java
List<Map<String, Object>> rows = people.stream()
.map(p -> Map.<String, Object>of("name", p.name(), "age", p.age()))
.toList();
driver.executableQuery("UNWIND $items AS item MERGE (p:Person {name: item.name}) SET p.age = item.age")
.withParameters(Map.of("items", rows))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute();参数map中允许的叶子类型:、///、/、、、、。自定义对象和必须先转换。
StringLongIntegerShortByteDoubleFloatBooleanList<?>Map<String,?>nullLocalDate将写操作分组到一个事务中 — 在一个中循环处理,而非每次迭代都调用。
executeWriteexecuteWrite连接池 — 默认100个连接。如果连接耗尽可调整:
java
Config.builder()
.withMaxConnectionPoolSize(50)
.withConnectionAcquisitionTimeout(30, TimeUnit.SECONDS)
.build()Common Errors
常见错误
| Mistake | Fix |
|---|---|
| String-interpolate Cypher params | |
| Omit database name | Set in |
New | Create once at startup; share everywhere |
Share | One session per request/thread |
Return | Collect to |
Leave | Consume before next call |
| Side effects in managed tx callback | Move outside — callback may retry |
| Pass custom objects to UNWIND params | Convert to |
| |
| |
Naked | Wrap in try/catch; use |
Assume | Commit uncertainty — design writes idempotent |
Block inside async callback ( | Chain with |
| Skip session close in async error path | |
| One transaction per write in loop | Batch with |
| Use |
| 错误操作 | 修复方案 |
|---|---|
| 拼接Cypher参数 | 始终使用 |
| 省略数据库名称 | 在 |
每个请求创建新 | 在启动时创建一次;全局共享 |
跨线程共享 | 每个请求/线程一个Session |
从事务回调返回 | 在回调内收集到 |
在执行下一个 | 在下一次调用前消费结果 |
| 在托管事务回调中产生副作用 | 移到回调外 — 回调可能重试 |
| 将自定义对象传递给UNWIND参数 | 转换为 |
对图数据库null调用 | 使用 |
对不存在的键调用 | 在访问可选结果列前检查 |
在catch块中直接调用 | 包裹在try/catch中;使用 |
假设 | 提交存在不确定性 — 将写操作设计为幂等 |
在异步回调中阻塞( | 使用 |
| 在异步错误路径中未关闭Session | 使用 |
| 循环中每次写操作都开启一个事务 | 使用 |
使用 | 使用 |
References
参考资料
Load on demand:
- references/async-reactive.md — full async patterns, reactive
CompletableFuturewithRxSession, deadlock avoidanceFlux.usingWhen - references/advanced-config.md — full options, TLS, notification filtering, session-level auth, user impersonation, cross-session bookmarks, spatial types (Values.point/WGS-84/Cartesian)
Config.builder()
Docs:
- Java Driver manual: https://neo4j.com/docs/java-manual/current/
- API reference: https://neo4j.com/docs/api/java-driver/current/
按需加载:
- references/async-reactive.md — 完整的异步模式、使用
CompletableFuture的响应式Flux.usingWhen、避免死锁RxSession - references/advanced-config.md — 完整的选项、TLS、通知过滤、会话级认证、用户模拟、跨会话书签、空间类型(Values.point/WGS-84/Cartesian)
Config.builder()
官方文档:
- Java Driver手册:https://neo4j.com/docs/java-manual/current/
- API参考:https://neo4j.com/docs/api/java-driver/current/
Checklist
检查清单
- One instance created at startup; closed on shutdown
Driver - called after driver creation
verifyConnectivity() - Database name specified in every /
QueryConfigSessionConfig - Parameters used (never string-interpolated Cypher)
- consumed inside managed transaction callback
Result - No side effects inside callbacks
executeRead/Write - Sessions closed via try-with-resources
- Async sessions closed in both success and error paths ()
exceptionallyCompose - on commit handled as commit-uncertain
ServiceUnavailableException - params are
UNWIND(no custom objects)List<Map<String,Object>> - checked before accessing optional result columns
containsKey()
- 在启动时创建一个实例;在关闭时销毁
Driver - 创建驱动后调用
verifyConnectivity() - 在每个/
QueryConfig中指定数据库名称SessionConfig - 使用参数传递(绝不拼接Cypher字符串)
- 在托管事务回调内消费
Result - 回调内无副作用
executeRead/Write - 通过try-with-resources关闭Session
- 异步Session在成功和错误路径中都关闭()
exceptionallyCompose - 处理提交时的(提交不确定性)
ServiceUnavailableException - UNWIND参数为(无自定义对象)
List<Map<String,Object>> - 访问可选结果列前检查
containsKey()