neo4j-driver-go-skill
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWhen to Use
适用场景
- Writing Go code that connects to Neo4j
- Setting up ,
neo4j.NewDriver(), or session/transaction patternsExecuteQuery() - Debugging connection errors, result iteration, type assertions, causal consistency
- 编写连接Neo4j的Go代码
- 配置 、
neo4j.NewDriver()或会话/事务模式ExecuteQuery() - 调试连接错误、结果迭代、类型断言、因果一致性问题
When NOT to Use
不适用场景
- Writing/optimizing Cypher →
neo4j-cypher-skill - v5→v6 migration steps →
neo4j-migration-skill
- 编写/优化Cypher查询 → 使用
neo4j-cypher-skill - v5→v6版本迁移步骤 → 使用
neo4j-migration-skill
Installation
安装
bash
go get github.com/neo4j/neo4j-go-driver/v6Import:
github.com/neo4j/neo4j-go-driver/v6/neo4jv5→v6 rename (deprecated aliases still compile, remove before v7):
| v5 | v6 |
|---|---|
| |
| |
bash
go get github.com/neo4j/neo4j-go-driver/v6导入:
github.com/neo4j/neo4j-go-driver/v6/neo4jv5→v6重命名说明(已废弃的别名仍可编译,但v7版本前需移除):
| v5 | v6 |
|---|---|
| |
| |
Environment Variables
环境变量
go
import "os"
uri := getEnv("NEO4J_URI", "neo4j://localhost:7687")
user := getEnv("NEO4J_USERNAME", "neo4j")
password := getEnv("NEO4J_PASSWORD", "")
database := getEnv("NEO4J_DATABASE", "neo4j")
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" { return v }
return fallback
}go
import "os"
uri := getEnv("NEO4J_URI", "neo4j://localhost:7687")
user := getEnv("NEO4J_USERNAME", "neo4j")
password := getEnv("NEO4J_PASSWORD", "")
database := getEnv("NEO4J_DATABASE", "neo4j")
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" { return v }
return fallback
}Driver Lifecycle
驱动生命周期
One per application. Goroutine-safe, connection-pooled, expensive to create.
Drivergo
func NewNeo4jDriver(uri, user, password string) (neo4j.Driver, error) {
driver, err := neo4j.NewDriver(
uri, // "neo4j+s://xxx.databases.neo4j.io" for Aura
neo4j.BasicAuth(user, password, ""),
)
if err != nil {
return nil, fmt.Errorf("create driver: %w", err)
}
ctx := context.Background()
if err := driver.VerifyConnectivity(ctx); err != nil {
driver.Close(ctx)
return nil, fmt.Errorf("verify connectivity: %w", err)
}
return driver, nil
}
// In main / app teardown:
defer driver.Close(ctx)❌ Never create driver per-request. Create once at startup; share across goroutines.
URI schemes: (Aura/TLS+routing), (plain+routing), (TLS+single), (plain+single).
neo4j+s://neo4j://bolt+s://bolt://每个应用仅需创建一个实例。该实例支持协程安全、连接池化,创建成本较高。
Drivergo
func NewNeo4jDriver(uri, user, password string) (neo4j.Driver, error) {
driver, err := neo4j.NewDriver(
uri, // Aura环境请使用 "neo4j+s://xxx.databases.neo4j.io"
neo4j.BasicAuth(user, password, ""),
)
if err != nil {
return nil, fmt.Errorf("create driver: %w", err)
}
ctx := context.Background()
if err := driver.VerifyConnectivity(ctx); err != nil {
driver.Close(ctx)
return nil, fmt.Errorf("verify connectivity: %w", err)
}
return driver, nil
}
// 在main函数/应用销毁阶段:
defer driver.Close(ctx)❌ 绝不要为每个请求创建驱动实例。应在启动时创建一次,在协程间共享使用。
URI协议:(Aura环境/TLS+路由)、(明文+路由)、(TLS+单节点)、(明文+单节点)。
neo4j+s://neo4j://bolt+s://bolt://Choosing the Right API
选择合适的API
| API | Use when | Auto-retry | Lazy results |
|---|---|---|---|
| Most queries — simple default | ✅ | ❌ eager |
| Large result sets / streaming | ✅ | ✅ |
| Spans multiple functions / ext coordination | ❌ | ✅ |
| | ❌ | ✅ |
CALL { … } IN TRANSACTIONSUSING PERIODIC COMMITsession.Run()| API | 适用场景 | 自动重试 | 延迟加载结果 |
|---|---|---|---|
| 大多数查询——简单默认选项 | ✅ | ❌ 立即加载 |
| 大型结果集/流式处理 | ✅ | ✅ |
| 事务操作跨多个函数/需外部协调 | ❌ | ✅ |
| 仅用于 | ❌ | ✅ |
CALL { … } IN TRANSACTIONSUSING PERIODIC COMMITsession.Run()ExecuteQuery (Recommended Default)
ExecuteQuery(推荐默认使用)
Manages sessions, transactions, retries, and bookmarks automatically.
go
result, err := neo4j.ExecuteQuery(ctx, driver,
`MATCH (p:Person {name: $name})-[:KNOWS]->(friend)
RETURN friend.name AS name`,
map[string]any{"name": "Alice"},
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"), // always specify
neo4j.ExecuteQueryWithReadersRouting(), // for read queries
)
if err != nil {
return fmt.Errorf("query people: %w", err)
}
for _, record := range result.Records {
name, _ := record.Get("name")
fmt.Println(name)
}
fmt.Println(result.Summary.Counters().NodesCreated())Key options:
go
neo4j.ExecuteQueryWithDatabase("mydb") // required for performance
neo4j.ExecuteQueryWithReadersRouting() // route reads to replicas
neo4j.ExecuteQueryWithImpersonatedUser("jane") // impersonate
neo4j.ExecuteQueryWithoutBookmarkManager() // opt out of causal consistency❌ Never concatenate user input into query strings. Always use parameters.
map[string]any自动管理会话、事务、重试以及书签。
go
result, err := neo4j.ExecuteQuery(ctx, driver,
`MATCH (p:Person {name: $name})-[:KNOWS]->(friend)
RETURN friend.name AS name`,
map[string]any{"name": "Alice"},
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"), // 务必指定
neo4j.ExecuteQueryWithReadersRouting(), // 只读查询使用
)
if err != nil {
return fmt.Errorf("query people: %w", err)
}
for _, record := range result.Records {
name, _ := record.Get("name")
fmt.Println(name)
}
fmt.Println(result.Summary.Counters().NodesCreated())关键选项:
go
neo4j.ExecuteQueryWithDatabase("mydb") // 对性能至关重要,必须指定
neo4j.ExecuteQueryWithReadersRouting() // 将读请求路由到副本节点
neo4j.ExecuteQueryWithImpersonatedUser("jane") // 模拟用户
neo4j.ExecuteQueryWithoutBookmarkManager() // 禁用因果一致性❌ 绝不要将用户输入拼接进查询字符串。始终使用参数。
map[string]anyManaged Transactions (Session-Based)
托管事务(基于会话)
Use for lazy streaming (large result sets) or callback-level control.
go
session := driver.NewSession(ctx, neo4j.SessionConfig{
DatabaseName: "neo4j", // always specify
AccessMode: neo4j.AccessModeRead,
})
defer session.Close(ctx)
result, err := session.ExecuteRead(ctx,
func(tx neo4j.ManagedTransaction) (any, error) {
res, err := tx.Run(ctx,
`MATCH (p:Person) RETURN p.name AS name LIMIT $limit`,
map[string]any{"limit": 100},
)
if err != nil {
return nil, err
}
var names []string
for res.Next(ctx) { // lazy — don't Collect() on large sets
name, _ := res.Record().Get("name")
names = append(names, name.(string))
}
return names, res.Err()
},
)❌ No side effects in callback — retried on transient failures.
→ replicas. → cluster leader.
ExecuteReadExecuteWrite适用于延迟加载流式处理(大型结果集)或需要回调级控制的场景。
go
session := driver.NewSession(ctx, neo4j.SessionConfig{
DatabaseName: "neo4j", // 务必指定
AccessMode: neo4j.AccessModeRead,
})
defer session.Close(ctx)
result, err := session.ExecuteRead(ctx,
func(tx neo4j.ManagedTransaction) (any, error) {
res, err := tx.Run(ctx,
`MATCH (p:Person) RETURN p.name AS name LIMIT $limit`,
map[string]any{"limit": 100},
)
if err != nil {
return nil, err
}
var names []string
for res.Next(ctx) { // 延迟加载——大型数据集请勿使用Collect()
name, _ := res.Record().Get("name")
names = append(names, name.(string))
}
return names, res.Err()
},
)❌ 回调函数中不要包含副作用——临时失败时会重试。
→ 请求路由到副本节点。 → 请求路由到集群主节点。
ExecuteReadExecuteWriteExplicit Transactions
显式事务
Use when transaction work spans multiple functions or requires external coordination.
go
session := driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: "neo4j"})
defer session.Close(ctx)
tx, err := session.BeginTransaction(ctx)
if err != nil {
return err
}
if err := doPartA(ctx, tx); err != nil {
tx.Rollback(ctx)
return err
}
if err := doPartB(ctx, tx); err != nil {
tx.Rollback(ctx)
return err
}
return tx.Commit(ctx)❌ Not auto-retried. Caller handles retry. Prefer managed transactions unless you need explicit control.
适用于事务操作跨多个函数或需要外部协调的场景。
go
session := driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: "neo4j"})
defer session.Close(ctx)
tx, err := session.BeginTransaction(ctx)
if err != nil {
return err
}
if err := doPartA(ctx, tx); err != nil {
tx.Rollback(ctx)
return err
}
if err := doPartB(ctx, tx); err != nil {
tx.Rollback(ctx)
return err
}
return tx.Commit(ctx)❌ 不会自动重试。需由调用方处理重试逻辑。除非需要显式控制,否则优先使用托管事务。
Error Handling
错误处理
go
result, err := neo4j.ExecuteQuery(...)
if err != nil {
var neo4jErr *neo4j.Neo4jError
if errors.As(err, &neo4jErr) {
slog.Error("database error", "code", neo4jErr.Code, "msg", neo4jErr.Msg)
}
var connErr *neo4j.ConnectivityError
if errors.As(err, &connErr) {
slog.Error("connectivity error", "err", connErr)
}
return fmt.Errorf("execute query: %w", err)
}Helpers:
go
neo4j.IsNeo4jError(err) // server-side Cypher/database error
neo4j.IsTransactionExecutionLimit(err) // managed tx retries exhaustedIn managed tx callback: return error → driver retries if transient.
at startup: check URI scheme, credentials, firewall.
ConnectivityErrorgo
result, err := neo4j.ExecuteQuery(...)
if err != nil {
var neo4jErr *neo4j.Neo4jError
if errors.As(err, &neo4jErr) {
slog.Error("database error", "code", neo4jErr.Code, "msg", neo4jErr.Msg)
}
var connErr *neo4j.ConnectivityError
if errors.As(err, &connErr) {
slog.Error("connectivity error", "err", connErr)
}
return fmt.Errorf("execute query: %w", err)
}辅助函数:
go
neo4j.IsNeo4jError(err) // 服务端Cypher/数据库错误
neo4j.IsTransactionExecutionLimit(err) // 托管事务重试次数耗尽在托管事务回调中:返回错误→驱动会在临时失败时重试。
启动时出现:检查URI协议、凭证、防火墙设置。
ConnectivityErrorData Types
数据类型映射
| Cypher | Go |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
go
// Typed extraction (v6+, preferred):
neo4j.GetRecordValue[string](record, "name")
// Manual extraction:
rawAge, ok := record.Get("age")
if !ok { return errors.New("missing 'age' field") }
age := rawAge.(int64) // Neo4j integers → int64
// Node access:
rawNode, _ := record.Get("p")
node := rawNode.(neo4j.Node)
name := node.Props["name"].(string)
labels := node.Labels // []string❌ Always check from before type-asserting — panics on missing key.
❌ After lazy loop, always check .
okrecord.Get()for res.Next(ctx)res.Err()| Cypher类型 | Go类型 |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
go
// 类型化提取(v6+,推荐使用):
neo4j.GetRecordValue[string](record, "name")
// 手动提取:
rawAge, ok := record.Get("age")
if !ok { return errors.New("missing 'age' field") }
age := rawAge.(int64) // Neo4j整数对应Go的int64
// 节点访问:
rawNode, _ := record.Get("p")
node := rawNode.(neo4j.Node)
name := node.Props["name"].(string)
labels := node.Labels // []string❌ 在类型断言前务必检查返回的值——缺少键会导致panic。
❌ 在延迟加载的循环结束后,务必检查。
record.Get()okfor res.Next(ctx)res.Err()Key Patterns
核心模式
Context — always propagate
上下文——始终传递
go
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
defer cancel()
// pass ctx to all driver callscontext.Background()go
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
defer cancel()
// 将ctx传递给所有驱动调用context.Background()Batching Writes
批量写入
go
// Bad: one transaction per record
for _, item := range items {
neo4j.ExecuteQuery(ctx, driver, writeQuery, item, ...)
}
// Good: UNWIND batch in one transaction
neo4j.ExecuteQuery(ctx, driver,
`UNWIND $items AS item
MERGE (n:Node {id: item.id})
SET n += item`,
map[string]any{"items": items},
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"),
)go
// 错误示例:每条记录对应一个事务
for _, item := range items {
neo4j.ExecuteQuery(ctx, driver, writeQuery, item, ...)
}
// 正确示例:在一个事务中使用UNWIND批量处理
neo4j.ExecuteQuery(ctx, driver,
`UNWIND $items AS item
MERGE (n:Node {id: item.id})
SET n += item`,
map[string]any{"items": items},
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"),
)Generic Helpers (v6+)
通用辅助函数(v6+)
Prefer type-safe helpers over manual assertions:
go
// GetRecordValue[T] — extract + cast in one call
name, isNil, err := neo4j.GetRecordValue[string](record, "name")
// isNil=true when OPTIONAL MATCH returned null; err != nil when key absent or wrong type
// CollectTWithContext — map all records to a slice
people, err := neo4j.CollectTWithContext(ctx, result, func(record *neo4j.Record) (Person, error) {
name, _, err := neo4j.GetRecordValue[string](record, "name")
age, _, _ := neo4j.GetRecordValue[int64](record, "age")
return Person{Name: name, Age: int(age)}, err
})
// SingleTWithContext — expect exactly one record (error if 0 or 2+)
person, err := neo4j.SingleTWithContext(ctx, result, func(record *neo4j.Record) (Person, error) {
name, _, _ := neo4j.GetRecordValue[string](record, "name")
return Person{Name: name}, nil
})
// GetProperty — typed property from Node or Relationship
node, _, _ := neo4j.GetRecordValue[neo4j.Node](record, "p")
nameVal, err := neo4j.GetProperty[string](node, "name")优先使用类型安全的辅助函数,而非手动断言:
go
// GetRecordValue[T] — 一步完成提取与类型转换
name, isNil, err := neo4j.GetRecordValue[string](record, "name")
// isNil=true表示OPTIONAL MATCH返回null;err!=nil表示键不存在或类型错误
// CollectTWithContext — 将所有记录映射为切片
people, err := neo4j.CollectTWithContext(ctx, result, func(record *neo4j.Record) (Person, error) {
name, _, err := neo4j.GetRecordValue[string](record, "name")
age, _, _ := neo4j.GetRecordValue[int64](record, "age")
return Person{Name: name, Age: int(age)}, err
})
// SingleTWithContext — 期望恰好一条记录(0条或2条以上会报错)
person, err := neo4j.SingleTWithContext(ctx, result, func(record *neo4j.Record) (Person, error) {
name, _, _ := neo4j.GetRecordValue[string](record, "name")
return Person{Name: name}, nil
})
// GetProperty — 从Node或Relationship中提取类型化属性
node, _, _ := neo4j.GetRecordValue[neo4j.Node](record, "p")
nameVal, err := neo4j.GetProperty[string](node, "name")Spatial Types
空间类型
go
// 2D Cartesian (SRID 7203), 3D Cartesian (SRID 9157)
pt2d := neo4j.Point2D{X: 1.23, Y: 4.56, SpatialRefId: 7203}
pt3d := neo4j.Point3D{X: 1.23, Y: 4.56, Z: 7.89, SpatialRefId: 9157}
// 2D WGS-84 (SRID 4326), 3D WGS-84 (SRID 4979)
london := neo4j.Point2D{X: -0.118092, Y: 51.509865, SpatialRefId: 4326}
shard := neo4j.Point3D{X: -0.0865, Y: 51.5045, Z: 310, SpatialRefId: 4979}
// Pass as parameter
result, err := neo4j.ExecuteQuery(ctx, driver,
"CREATE (p:Place {location: $loc})",
map[string]any{"loc": london},
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"),
)
// Read from result — assert to Point2D or Point3D
raw, _ := record.Get("location")
if p2d, ok := raw.(neo4j.Point2D); ok {
fmt.Printf("lon=%f lat=%f srid=%d\n", p2d.X, p2d.Y, p2d.SpatialRefId)
}
// Distance (same SRID only)
result, _ = neo4j.ExecuteQuery(ctx, driver,
"RETURN point.distance($p1, $p2) AS distance",
map[string]any{"p1": pt2d, "p2": neo4j.Point2D{X: 10, Y: 10, SpatialRefId: 7203}},
neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase("neo4j"),
)
dist, _ := result.Records[0].Get("distance")
fmt.Println(dist.(float64))go
// 2D笛卡尔坐标(SRID 7203),3D笛卡尔坐标(SRID 9157)
pt2d := neo4j.Point2D{X: 1.23, Y: 4.56, SpatialRefId: 7203}
pt3d := neo4j.Point3D{X: 1.23, Y: 4.56, Z: 7.89, SpatialRefId: 9157}
// 2D WGS-84(SRID 4326),3D WGS-84(SRID 4979)
london := neo4j.Point2D{X: -0.118092, Y: 51.509865, SpatialRefId: 4326}
shard := neo4j.Point3D{X: -0.0865, Y: 51.5045, Z: 310, SpatialRefId: 4979}
// 作为参数传递
result, err := neo4j.ExecuteQuery(ctx, driver,
"CREATE (p:Place {location: $loc})",
map[string]any{"loc": london},
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"),
)
// 从结果中读取——断言为Point2D或Point3D
raw, _ := record.Get("location")
if p2d, ok := raw.(neo4j.Point2D); ok {
fmt.Printf("lon=%f lat=%f srid=%d\n", p2d.X, p2d.Y, p2d.SpatialRefId)
}
// 计算距离(仅支持相同SRID)
result, _ = neo4j.ExecuteQuery(ctx, driver,
"RETURN point.distance($p1, $p2) AS distance",
map[string]any{"p1": pt2d, "p2": neo4j.Point2D{X: 10, Y: 10, SpatialRefId: 7203}},
neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase("neo4j"),
)
dist, _ := result.Records[0].Get("distance")
fmt.Println(dist.(float64))Always Specify Database
务必指定数据库
go
neo4j.ExecuteQueryWithDatabase("neo4j") // in ExecuteQuery
neo4j.SessionConfig{DatabaseName: "neo4j"} // in sessionsOmitting costs a network round-trip per call to resolve home database.
go
neo4j.ExecuteQueryWithDatabase("neo4j") // 在ExecuteQuery中指定
neo4j.SessionConfig{DatabaseName: "neo4j"} // 在会话中指定省略数据库名会导致每次调用都需要额外的网络往返来解析默认数据库。
Causal Consistency
因果一致性
ExecuteQueryExecuteQueryCommon Errors
常见错误
| Error / Symptom | Cause | Fix |
|---|---|---|
| URI wrong / TLS mismatch / firewall | Check scheme ( |
| Pool exhausted | Increase |
| Panic on type assertion | | Use |
| Network error mid-stream | Handle error; re-run transaction |
| Callback retried unexpectedly | Side effect inside managed tx | Move side effects outside callback |
| Context deadline exceeded | No timeout on context | Use |
| 0 results, query looks correct | Wrong | Always set |
| Run inside managed tx | Use |
| 错误/症状 | 原因 | 解决方法 |
|---|---|---|
启动时出现 | URI错误/TLS不匹配/防火墙拦截 | 检查协议(Aura环境使用 |
运行中出现 | 连接池耗尽 | 增大 |
| 类型断言导致panic | | 使用 |
循环结束后 | 流式处理中出现网络错误 | 处理错误;重新执行事务 |
| 回调函数意外重试 | 托管事务回调中包含副作用 | 将副作用移到回调函数外部 |
| 上下文超时 | 未设置上下文超时 | 使用 |
| 查询语句正确但返回0结果 | | 始终在配置中设置 |
| 在托管事务中运行 | 使用 |
References
参考资料
Load on demand:
Load on demand:
- references/advanced-config.md — connection pool tuning, custom address resolver, notification config, Bolt logging, auth options, URI scheme table
- references/repository-pattern.md — repository wrapper pattern, cross-session causal consistency with bookmarks
按需加载:
- references/advanced-config.md — 连接池调优、自定义地址解析器、通知配置、Bolt日志、认证选项、URI协议对照表
- references/repository-pattern.md — 仓库包装模式、跨会话书签因果一致性
WebFetch
在线资源
| Need | URL |
|---|---|
| Go driver manual | |
| API reference | |
| 需求 | 链接 |
|---|---|
| Go驱动手册 | |
| API参考 | |
Checklist
检查清单
- One driver created at startup; shared across goroutines;
defer driver.Close(ctx) - called at startup
driver.VerifyConnectivity(ctx) - set in all
DatabaseName/SessionConfigExecuteQueryWithDatabase - used for production queries
context.WithTimeout - parameters used — no string interpolation
map[string]any - on read-only
ExecuteQueryWithReadersRouting()callsExecuteQuery - checked after lazy
res.Err()loopfor result.Next(ctx) - Type assertions guarded (use or check
GetRecordValue[T])ok - No side effects inside managed transaction callbacks
- used for
session.Run()/ auto-commit queriesCALL IN TRANSACTIONS - Sessions closed with
defer session.Close(ctx)
- 启动时创建一个驱动实例,在协程间共享,使用确保关闭
defer driver.Close(ctx) - 启动时调用
driver.VerifyConnectivity(ctx) - 所有/
SessionConfig中都设置ExecuteQueryWithDatabaseDatabaseName - 生产环境查询使用
context.WithTimeout - 使用参数——禁止字符串插值
map[string]any - 只读调用使用
ExecuteQueryExecuteQueryWithReadersRouting() - 延迟加载的循环结束后检查
for result.Next(ctx)res.Err() - 类型断言前进行防护(使用或检查
GetRecordValue[T]值)ok - 托管事务回调中不包含副作用
- /自动提交查询使用
CALL IN TRANSACTIONSsession.Run() - 使用关闭会话
defer session.Close(ctx)