mcpField
N
neo4j-driver-java-skill

neo4j-driver-java-skill

github.com/neo4j-contrib开发118781 次安装1 周前更新
代码仓库

关于 neo4j-driver-java-skill

面向通过 Java 或 Kotlin 连接 Neo4j 的开发者的技能指南,涵盖驱动、同步与异步访问、事务和结果映射,并提供错误处理与性能建议。

主要功能

  1. 01配置 Neo4j Java Driver
  2. 02选择查询、事务及访问模式
  3. 03处理结果、数据类型与映射
  4. 04排查连接和事务错误
  5. 05批量写入及连接池调优

适用场景

  1. 01在 Java 或 Kotlin 项目中连接 Neo4j
  2. 02处理大型查询结果或流式读取
  3. 03为多方法操作管理显式事务
  4. 04排查连接池或结果处理问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/neo4j-contrib/neo4j-skills --skill neo4j-driver-java-skill

安装后在 Claude Code 中输入/neo4j-driver-java-skill确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
references/
advanced-config.md
async-reactive.md
property-encryption.md

详细介绍

适用场景

  • 使用 Java/Kotlin 代码连接 Neo4j(Aura 或自行管理的实例)
  • 在 Maven/Gradle 项目中配置驱动程序、会话和事务
  • 调试结果处理、错误恢复和连接池问题
  • 通过异步(CompletableFuture)或响应式(Project Reactor / RxJava)方式访问 Neo4j

不适用场景

  • 编写/优化 Cypher 查询 → neo4j-cypher-skill
  • 升级驱动程序版本 → neo4j-migration-skill
  • Spring Data Neo4j(@Node、@Relationship、Neo4jRepository)→ neo4j-spring-data-skill

依赖项

Maven
<dependency>
    <groupId>org.neo4j.driver</groupId>
    <artifactId>neo4j-java-driver</artifactId>
    <version>6.3.0</version>
</dependency>
Gradle
implementation 'org.neo4j.driver:neo4j-java-driver:6.3.0'

查看最新版本:https://central.sonatype.com/artifact/org.neo4j.driver/neo4j-java-driver

6.2.0 [2026-06]:支持 Neo4j UUID 类型和 Bolt 6.1;结果摘要中包含 QueryProfile。 6.2.1 [2026-08]:修复了 UUID 值的 Value#asObject() 问题——如果要以通用方式读取 UUID 属性,则需要此版本。Neo4j 2026.08 服务端捆绑了 6.2.1;2026.07 捆绑了 6.2.0。 6.3.0 [2026-09]:预览属性加密功能——对属性值进行客户端信封加密(driver.propertyEncryption())→ references/property-encryption.md。

服务端 UUID 类型和 uuid() 构造函数随 Neo4j 2026.08 一同发布——连接这些服务端时,请使用 >= 6.2.1 版本的驱动程序。


环境变量

连接配置的标准做法——切勿硬编码凭据:

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 注入:

spring.neo4j.uri=neo4j+s://xxx.databases.neo4j.io
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret

驱动程序生命周期

每个应用程序使用一个 Driver——它是线程安全的,但创建开销较大。实现 AutoCloseable 或使用 try-with-resources。

// 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 方案:

URI用途
neo4j://localhost不加密,集群路由
neo4j+s://xxx.databases.neo4j.ioTLS + 集群路由(Aura)
bolt://localhost:7687不加密,单实例
bolt+s://localhost:7687TLS,单实例

身份验证选项:AuthTokens.basic(u,p) · AuthTokens.bearer(token) · AuthTokens.kerberos(b64) · AuthTokens.none()


选择合适的 API

API适用场景自动重试流式处理
driver.executableQuery()大多数查询的默认选择✅❌ 预先执行
session.executeRead/Write()大型结果集、需要回调控制✅✅
session.beginTransaction()多方法操作、外部协调❌✅
session.run()自行管理的查询(CALL IN TRANSACTIONS)⚠️ 一次性 [6.1+]✅
driver.asyncSession()非阻塞 CompletableFuture✅✅
driver.rxSession()Reactor/RxJava 背压✅✅

CALL { … } IN TRANSACTIONS 和 USING PERIODIC COMMIT 会自行管理事务——仅对这些查询使用 session.run()。executableQuery 和 executeRead/Write 执行这些查询时会失败。

session.run() 重试 [6.1+]:仅针对幂等错误立即重试一次(默认启用)。可针对每个驱动程序或会话禁用:

// Driver-level — disable for all sessions
var config = Config.builder().withAutoCommitRetriesDisabled(true).build();

// Session-level — overrides driver
var sessionConfig = SessionConfig.builder()
    .withAutoCommitRetriesMode(AutoCommitRetriesMode.DISABLED)  // DEFAULT = follow driver
    .build();

executableQuery — 默认选择

// 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();

切勿使用字符串插值拼接 Cypher。始终使用 .withParameters(Map.of(...))。


托管事务(executeRead / executeWrite)

会话不具备线程安全性——每个请求/线程使用一个会话,并始终关闭。

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"))
    );
}
必须在回调内部使用完结果

Result 是与打开的事务关联的惰性游标。回调返回时事务会关闭——此后读取会抛出 ResultConsumedException。

// ❌ 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());
回调规则
  • 在下一次调用 tx.run() 前使用完每个 Result——同时打开多个游标会导致行为未定义。
  • 不要产生副作用(HTTP、电子邮件、指标递增)——发生瞬时错误时,回调可能会重试。
  • 使用 MERGE(幂等),而不是 CREATE,以确保写入可安全重试。
  • executeRead → 副本;executeWrite → 主节点。
TransactionConfig — 超时和元数据
var config = TransactionConfig.builder()
    .withTimeout(Duration.ofSeconds(5))
    .withMetadata(Map.of("app", "myService", "user", userId))  // visible in SHOW TRANSACTIONS
    .build();
session.executeRead(tx -> { /* ... */ }, config);

显式事务

适用于跨越多个方法或需要外部协调的工作。不会自动重试。

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 成功后提交)

错误处理

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
}

托管事务会自动重试 TransientException——无需捕获。


数据类型与值提取

Cypher 类型Java 访问器
Integervalue.asLong() / value.asInt()
Floatvalue.asDouble()
Stringvalue.asString()
Booleanvalue.asBoolean()
Listvalue.asList()
Mapvalue.asMap()
Nodevalue.asNode()
Relationshipvalue.asRelationship()
Datevalue.asLocalDate()
DateTimevalue.asZonedDateTime()
UUIDvalue.asUUID() → java.util.UUID [6.2+, Neo4j 2026.08+]
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();
空值安全——两种不同的情况
情况record.get(key).asString()
键存在,值非空该值返回字符串
键存在,值为图空值.isNull() = true 的 Value抛出 Uncoercible
键不存在(拼写错误 / 未投影)Value.NULL 哨兵值抛出 NoSuchElementException
// 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();
}

对象映射

将查询结果直接映射到 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"));

嵌套映射——返回映射投影,并在列表中加入 COLLECT {}:

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 返回的额外属性会被忽略。


性能模式

始终指定数据库——省略数据库会导致每次调用都往返查询主数据库。

将读取路由到副本——在 QueryConfig 中使用 RoutingControl.READ,或使用 executeRead。

使用 UNWIND 批量写入——传递 List<Map<String,Object>>(仅支持普通 map;自定义对象会失败):

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 个连接。连接耗尽时可调整:

Config.builder()
    .withMaxConnectionPoolSize(50)
    .withConnectionAcquisitionTimeout(30, TimeUnit.SECONDS)
    .build()

常见错误

错误做法修复方法
通过字符串插值传入 Cypher 参数始终使用 .withParameters(Map.of(...))
未指定数据库名称每次都在 QueryConfig / SessionConfig 中设置
每次请求都创建新的 Driver在启动时创建一次,并在所有地方共享
在线程间共享 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 链接
异步错误路径中未关闭会话使用 exceptionallyCompose 关闭会话,然后重新抛出异常
在循环中每次写入都开启一个事务使用 UNWIND 批处理,或在一个回调中分组处理
对读取操作使用 executeWrite使用 executeRead——将请求路由到副本

参考资料

按需加载:

  • references/async-reactive.md — 完整的异步 CompletableFuture 模式、结合 Flux.usingWhen 的响应式 RxSession、死锁规避
  • references/property-encryption.md — 属性加密预览版 [6.3.0+]:信封配置、密钥封装服务(本地、AWS KMS、Azure Key Vault、GCP KMS)、密钥管理器、使用 AAD 加密/解密
  • references/advanced-config.md — 完整的 Config.builder() 选项、TLS、通知过滤、会话级身份验证、用户模拟、跨会话书签、空间类型(Values.point/WGS-84/Cartesian)

文档:


检查清单

  • 在启动时创建一个 Driver 实例;在关闭时将其关闭
  • 创建驱动程序后调用 verifyConnectivity()
  • 在每个 QueryConfig / SessionConfig 中指定数据库名称
  • 使用参数(绝不通过字符串插值传入 Cypher)
  • 在托管事务回调中消费 Result
  • 在 executeRead/Write 回调中不执行副作用
  • 使用 try-with-resources 关闭会话
  • 在成功和错误路径中都关闭异步会话(exceptionallyCompose)
  • 将提交时的 ServiceUnavailableException 视为提交状态不确定
  • UNWIND 参数使用 List<Map<String,Object>>(不使用自定义对象)
  • 访问可选结果列前先检查 containsKey()

评论

支持 Markdown