neo4j-driver-java-skill

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

When 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 (
    CompletableFuture
    ) or reactive (Project Reactor / RxJava) Neo4j access
  • 使用Java/Kotlin代码连接Neo4j(Aura或自托管版本)
  • 在Maven/Gradle项目中配置驱动、会话和事务
  • 调试结果处理、错误恢复和连接池问题
  • 异步(
    CompletableFuture
    )或响应式(Project Reactor / RxJava)方式访问Neo4j

When NOT to Use

不适用场景

  • Cypher query authoring/optimization
    neo4j-cypher-skill
  • Driver version upgrades
    neo4j-migration-skill
  • Spring Data Neo4j (
    @Node
    ,
    @Relationship
    ,
    Neo4jRepository
    ) →
    neo4j-spring-data-skill

  • Cypher查询编写/优化 → 使用
    neo4j-cypher-skill
  • 驱动版本升级 → 使用
    neo4j-migration-skill
  • Spring Data Neo4j
    @Node
    @Relationship
    Neo4jRepository
    )→ 使用
    neo4j-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
@Value("${spring.neo4j.uri}")
or
application.properties
:
properties
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.properties
注入:
properties
spring.neo4j.uri=neo4j+s://xxx.databases.neo4j.io
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret

Driver Lifecycle

驱动生命周期

One
Driver
per application — thread-safe, expensive to create. Implement
AutoCloseable
or use try-with-resources.
java
// 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:
URIUse
neo4j://localhost
Unencrypted, cluster routing
neo4j+s://xxx.databases.neo4j.io
TLS + cluster routing (Aura)
bolt://localhost:7687
Unencrypted, single instance
bolt+s://localhost:7687
TLS, single instance
Auth options:
AuthTokens.basic(u,p)
·
AuthTokens.bearer(token)
·
AuthTokens.kerberos(b64)
·
AuthTokens.none()

每个应用仅需一个
Driver
实例 — 线程安全,创建成本高。实现
AutoCloseable
接口或使用try-with-resources语法。
java
// 长期存活的单例实例
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用途
neo4j://localhost
未加密,集群路由
neo4j+s://xxx.databases.neo4j.io
TLS + 集群路由(Aura)
bolt://localhost:7687
未加密,单实例
bolt+s://localhost:7687
TLS,单实例
认证选项:
AuthTokens.basic(u,p)
·
AuthTokens.bearer(token)
·
AuthTokens.kerberos(b64)
·
AuthTokens.none()

Choosing the Right API

选择合适的API

APIWhenAuto-retryStreaming
driver.executableQuery()
Default for most queries❌ eager
session.executeRead/Write()
Large results, callback control
session.beginTransaction()
Multi-method, external coordination
session.run()
Self-managing queries (
CALL IN TRANSACTIONS
)
driver.asyncSession()
Non-blocking
CompletableFuture
driver.rxSession()
Reactor/RxJava backpressure
CALL { … } IN TRANSACTIONS
and
USING PERIODIC COMMIT
self-manage their transaction — use
session.run()
only.
executableQuery
and
executeRead/Write
will fail for these queries.

API适用场景自动重试流式处理
driver.executableQuery()
大多数查询的默认选择❌ 立即加载
session.executeRead/Write()
大结果集、回调控制
session.beginTransaction()
跨方法操作、外部协调
session.run()
自我管理的查询(
CALL IN TRANSACTIONS
driver.asyncSession()
非阻塞
CompletableFuture
driver.rxSession()
Reactor/RxJava背压处理
CALL { … } IN TRANSACTIONS
USING PERIODIC COMMIT
会自行管理事务 — 仅使用
session.run()
executableQuery
executeRead/Write
执行这些查询会失败。

executableQuery
— Default

executableQuery
— 默认推荐

java
// 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
)

托管事务(
executeRead
/
executeWrite

Sessions 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

结果必须在回调内消费

Result
is a lazy cursor tied to the open transaction. Transaction closes when callback returns — any read after that throws
ResultConsumedException
.
java
// ❌ 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());
Result
是与打开的事务绑定的惰性游标。回调返回后事务关闭 — 之后的任何读取操作都会抛出
ResultConsumedException
java
// ❌ 返回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
    Result
    before next
    tx.run()
    — multiple open cursors = undefined behaviour.
  • No side effects (HTTP, email, metric increments) — callback may be retried on transient errors.
  • Use
    MERGE
    (idempotent), not
    CREATE
    , for retry-safe writes.
  • executeRead
    → replica;
    executeWrite
    → leader.
  • 在执行下一个
    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()
is a network call — wrap in its own try/catch and use
addSuppressed
so the original exception is not lost.
Commit uncertainty: if
tx.commit()
throws
ServiceUnavailableException
, the commit may or may not have succeeded. Design writes as idempotent (
MERGE
+ unique constraints) so retrying is safe.
Choose explicit vs managed:
  • Auto-retry needed →
    executeRead
    /
    executeWrite
  • Work spans multiple methods → explicit (pass
    tx
    as parameter)
  • 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()
是网络调用 — 需包裹在独立的try/catch中,并使用
addSuppressed
避免丢失原始异常。
提交不确定性:如果
tx.commit()
抛出
ServiceUnavailableException
,提交可能成功也可能失败。将写操作设计为幂等(
MERGE
+ 唯一约束),确保重试安全。
选择显式事务还是托管事务:
  • 需要自动重试 → 使用
    executeRead
    /
    executeWrite
  • 操作跨多个方法 → 使用显式事务(将
    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
TransientException
— no catch needed.

java
try {
    driver.executableQuery("...").execute();
} catch (ServiceUnavailableException e) {
    // 无可用服务器 — 检查连接
} catch (SessionExpiredException e) {
    // 服务器关闭会话 — 开启新会话
} catch (TransientException e) {
    // 托管事务会自动重试;显式事务需手动重试
} catch (Neo4jException e) {
    // Cypher/约束错误 — e.code()返回GQL状态码
}
托管事务会自动重试
TransientException
— 无需捕获。

Data Types & Value Extraction

数据类型与值提取

Cypher typeJava accessor
Integer
value.asLong()
/
value.asInt()
Float
value.asDouble()
String
value.asString()
Boolean
value.asBoolean()
List
value.asList()
Map
value.asMap()
Node
value.asNode()
Relationship
value.asRelationship()
Date
value.asLocalDate()
DateTime
value.asZonedDateTime()
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访问方法
Integer
value.asLong()
/
value.asInt()
Float
value.asDouble()
String
value.asString()
Boolean
value.asBoolean()
List
value.asList()
Map
value.asMap()
Node
value.asNode()
Relationship
value.asRelationship()
Date
value.asLocalDate()
DateTime
value.asZonedDateTime()
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
record.get(key)
.asString()
Key present, value non-nullthe valuereturns string
Key present, value is graph null
Value
where
.isNull()
= true
throws
Uncoercible
Key absent (typo / not projected)
Value.NULL
sentinel
throws
NoSuchElementException
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();
}

场景
record.get(key)
.asString()
键存在,值非null返回对应值返回字符串
键存在,值为图数据库null返回
.isNull()
为true的
Value
抛出
Uncoercible
异常
键不存在(拼写错误/未投影)返回
Value.NULL
哨兵值
抛出
NoSuchElementException
异常
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
COLLECT {}
for lists:
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
RoutingControl.READ
in
QueryConfig
or use
executeRead
.
Batch writes with
UNWIND
— pass
List<Map<String,Object>>
(plain maps only; custom objects fail):
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:
String
,
Long
/
Integer
/
Short
/
Byte
,
Double
/
Float
,
Boolean
,
List<?>
,
Map<String,?>
,
null
. Custom objects and
LocalDate
must be converted first.
Group writes in one transaction — one
executeWrite
with a loop, not one
executeWrite
per iteration.
Connection pool — default 100 connections. Tune if exhausted:
java
Config.builder()
    .withMaxConnectionPoolSize(50)
    .withConnectionAcquisitionTimeout(30, TimeUnit.SECONDS)
    .build()

始终指定数据库 — 省略会导致每次调用都触发主数据库往返。
将读操作路由到副本节点 — 在
QueryConfig
中设置
RoutingControl.READ
或使用
executeRead
使用
UNWIND
批量写入
— 传递
List<Map<String,Object>>
(仅支持普通map;自定义对象会失败):
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中允许的叶子类型:
String
Long
/
Integer
/
Short
/
Byte
Double
/
Float
Boolean
List<?>
Map<String,?>
null
。自定义对象和
LocalDate
必须先转换。
将写操作分组到一个事务中 — 在一个
executeWrite
中循环处理,而非每次迭代都调用
executeWrite
连接池 — 默认100个连接。如果连接耗尽可调整:
java
Config.builder()
    .withMaxConnectionPoolSize(50)
    .withConnectionAcquisitionTimeout(30, TimeUnit.SECONDS)
    .build()

Common Errors

常见错误

MistakeFix
String-interpolate Cypher params
.withParameters(Map.of(...))
always
Omit database nameSet in
QueryConfig
/
SessionConfig
every time
New
Driver
per request
Create once at startup; share everywhere
Share
Session
across threads
One session per request/thread
Return
Result
from tx callback
Collect to
List
/
Map
inside callback
Leave
Result
open before next
tx.run()
Consume before next call
Side effects in managed tx callbackMove outside — callback may retry
Pass custom objects to UNWIND paramsConvert to
List<Map<String,Object>>
asString()
on graph null
.asString("default")
or check
.isNull()
asString()
on absent key
containsKey()
before optional access
Naked
tx.rollback()
in catch
Wrap in try/catch; use
addSuppressed
Assume
commit()
failure = no commit
Commit uncertainty — design writes idempotent
Block inside async callback (
.join()
)
Chain with
thenCompose
Skip session close in async error path
exceptionallyCompose
to close then re-throw
One transaction per write in loopBatch with
UNWIND
or group in one callback
executeWrite
for a read
Use
executeRead
— routes to replica

错误操作修复方案
拼接Cypher参数始终使用
.withParameters(Map.of(...))
省略数据库名称
QueryConfig
/
SessionConfig
中每次都指定
每个请求创建新
Driver
在启动时创建一次;全局共享
跨线程共享
Session
每个请求/线程一个Session
从事务回调返回
Result
在回调内收集到
List
/
Map
在执行下一个
tx.run()
前未关闭
Result
在下一次调用前消费结果
在托管事务回调中产生副作用移到回调外 — 回调可能重试
将自定义对象传递给UNWIND参数转换为
List<Map<String,Object>>
对图数据库null调用
asString()
使用
.asString("default")
或检查
.isNull()
对不存在的键调用
asString()
在访问可选结果列前检查
containsKey()
在catch块中直接调用
tx.rollback()
包裹在try/catch中;使用
addSuppressed
假设
commit()
失败则提交未成功
提交存在不确定性 — 将写操作设计为幂等
在异步回调中阻塞(
.join()
使用
thenCompose
链式调用
在异步错误路径中未关闭Session使用
exceptionallyCompose
关闭后重新抛出
循环中每次写操作都开启一个事务使用
UNWIND
批量处理或分组到一个回调中
使用
executeWrite
执行读操作
使用
executeRead
— 路由到副本节点

References

参考资料

Load on demand:
  • references/async-reactive.md — full async
    CompletableFuture
    patterns, reactive
    RxSession
    with
    Flux.usingWhen
    , deadlock avoidance
  • references/advanced-config.md — full
    Config.builder()
    options, TLS, notification filtering, session-level auth, user impersonation, cross-session bookmarks, spatial types (Values.point/WGS-84/Cartesian)
Docs:

按需加载:
  • references/async-reactive.md — 完整的异步
    CompletableFuture
    模式、使用
    Flux.usingWhen
    的响应式
    RxSession
    、避免死锁
  • references/advanced-config.md — 完整的
    Config.builder()
    选项、TLS、通知过滤、会话级认证、用户模拟、跨会话书签、空间类型(Values.point/WGS-84/Cartesian)
官方文档:

Checklist

检查清单

  • One
    Driver
    instance created at startup; closed on shutdown
  • verifyConnectivity()
    called after driver creation
  • Database name specified in every
    QueryConfig
    /
    SessionConfig
  • Parameters used (never string-interpolated Cypher)
  • Result
    consumed inside managed transaction callback
  • No side effects inside
    executeRead/Write
    callbacks
  • Sessions closed via try-with-resources
  • Async sessions closed in both success and error paths (
    exceptionallyCompose
    )
  • ServiceUnavailableException
    on commit handled as commit-uncertain
  • UNWIND
    params are
    List<Map<String,Object>>
    (no custom objects)
  • containsKey()
    checked before accessing optional result columns
  • 在启动时创建一个
    Driver
    实例;在关闭时销毁
  • 创建驱动后调用
    verifyConnectivity()
  • 在每个
    QueryConfig
    /
    SessionConfig
    中指定数据库名称
  • 使用参数传递(绝不拼接Cypher字符串)
  • 在托管事务回调内消费
    Result
  • executeRead/Write
    回调内无副作用
  • 通过try-with-resources关闭Session
  • 异步Session在成功和错误路径中都关闭(
    exceptionallyCompose
  • 处理提交时的
    ServiceUnavailableException
    (提交不确定性)
  • UNWIND参数为
    List<Map<String,Object>>
    (无自定义对象)
  • 访问可选结果列前检查
    containsKey()