研发协作
用 Go 连接 Neo4j 时如何管理驱动生命周期、事务…
编写 Go 代码连接 Neo4j(neo4j-go-driver v6)时,正确使用驱动、会话、事务、参数化查询与类型转换的完整做法。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
用 Go 连接 Neo4j 时如何管理驱特定
解决思路
- 安装驱动:`go get github.com/neo4j/neo4j-go-driver/v6`,导入 `github.com/neo4j/neo4j-go-driver/v6/neo4j`。
- v5→v6 重命名:`neo4j.NewDriverWithContext(...)` 改为 `neo4j.NewDriver(...)`,`neo4j.DriverWithContext` 改为 `neo4j.Driver`(废弃别名仍可编译,v7 前移除)。
- 从环境变量读取连接信息:`NEO4J_URI`(默认 `neo4j://localhost:7687`)、`NEO4J_USERNAME`(默认 `neo4j`)、`NEO4J_PASSWORD`、`NEO4J_DATABASE`(默认 `neo4j`),用 `getEnv(key, fallback)` 辅助函数;开发环境用 godotenv 加载 `.env`,并将 `.env` 加入 `.gitignore`。
- 驱动生命周期:每个应用只创建一个 `Driver`(goroutine 安全、带连接池、创建开销大),用 `neo4j.NewDriver(uri, neo4j.BasicAuth(user, password, ""))` 创建,启动时调用 `driver.VerifyConnectivity(ctx)` 验证,失败则 `driver.Close(ctx)`,在 main/应用退出时 `defer driver.Close(ctx)`。
- URI 方案选择:`neo4j+s://`(Aura/TLS+路由)、`neo4j://`(明文+路由)、`bolt+s://`(TLS+单节点)、`bolt://`(明文+单节点)。
- API 选择:`neo4j.ExecuteQuery()` 用于大多数查询(自动重试、结果急切加载);`session.ExecuteRead/Write()` 用于大结果集/流式(自动重试、惰性结果);`session.BeginTransaction()` 用于跨函数或需外部协调的事务(不自动重试、惰性结果);`session.Run()` 用于 `CALL IN TRANSACTIONS` 或自动提交查询。
- `CALL { … } IN TRANSACTIONS` 和 `USING PERIODIC COMMIT` 自行管理事务,必须用 `session.Run()`,在托管事务中会失败。
- ExecuteQuery 用法:传入 ctx、driver、查询字符串、`map[string]any` 参数、`neo4j.EagerResultTransformer`,并加选项 `neo4j.ExecuteQueryWithDatabase("neo4j")`(始终指定)、读查询加 `neo4j.ExecuteQueryWithReadersRouting()`;遍历 `result.Records` 用 `record.Get("name")`,用 `result.Summary.Counters().NodesCreated()` 获取统计。
- ExecuteQuery 其他选项:`neo4j.ExecuteQueryWithImpersonatedUser("jane")` 模拟用户,`neo4j.ExecuteQueryWithoutBookmarkManager()` 退出因果一致性。
- 绝不把用户输入拼接进查询字符串,始终用 `map[string]any` 参数。
- 结构体参数(v6.2.0+,Object Mapping 预览):`params` 中的结构体(或指针)会作为 Cypher map 发送,同样适用于 `Session.Run`、`ManagedTransaction.Run`、`ExplicitTransaction.Run`;用 `neo4j:"name"` 标签映射字段,`neo4j:"age,omitempty"` 在零值时丢弃,`neo4j:"-"` 永不发送。
- 托管事务:用 `driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: "neo4j", AccessMode: neo4j.AccessModeRead})` 创建会话并 `defer session.Close(ctx)`,用 `session.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (any, error) {...})` 执行;大结果集用 `for res.Next(ctx)` 惰性遍历而非 `Collect()`;`ExecuteRead` 路由到副本,`ExecuteWrite` 路由到集群 leader。
- 托管事务回调中不要有副作用——瞬时失败会重试回调。
- 显式事务:用 `session.BeginTransaction(ctx)` 开启,跨多个函数执行,任一步失败调用 `tx.Rollback(ctx)`,全部成功调用 `tx.Commit(ctx)`;显式事务不自动重试,由调用方处理重试,除非需要显式控制否则优先用托管事务。
- 错误处理:用 `errors.As(err, &neo4jErr)` 匹配 `*neo4j.Neo4jError`(含 `Code`、`Msg`),用 `errors.As(err, &connErr)` 匹配 `*neo4j.ConnectivityError`;辅助函数 `neo4j.IsNeo4jError(err)` 判断服务端 Cypher/数据库错误,`neo4j.IsTransactionExecutionLimit(err)` 判断托管事务重试耗尽;托管事务回调返回错误时,瞬时错误驱动会自动重试;启动时 `ConnectivityError` 检查 URI 方案、凭据、防火墙。
- 数据类型映射:`Integer`→`int64`,`Float`→`float64`,`String`→`string`,`Boolean`→`bool`,`List`→`[]any`,`Map`→`map[string]any`,`Node`→`neo4j.Node`,`Relationship`→`neo4j.Relationship`,`Path`→`neo4j.Path`,`Date`→`neo4j.Date`,`DateTime`→`neo4j.Time`,`Duration`→`neo4j.Duration`,`UUID`→`neo4j.UUID`(即 `dbtype.UUID`,`[16]byte` RFC 9562,v6.2.0+、Bolt 6.1,用 `dbtype.ParseUUID(s)` 解析、`.String()` 渲染),`null`→`nil`。
- 类型提取:优先用 `neo4j.GetRecordValue[string](record, "name")`;手动提取时先 `rawAge, ok := record.Get("age")` 检查 `ok` 再断言 `rawAge.(int64)`;节点访问用 `rawNode.(neo4j.Node)`,属性 `node.Props["name"].(string)`,标签 `node.Labels`。
- 始终在 `record.Get()` 后检查 `ok` 再类型断言,否则缺失键会 panic;惰性 `for res.Next(ctx)` 循环后始终检查 `res.Err()`。
- 上下文传播:用 `context.WithTimeout(parentCtx, 5*time.Second)` 并 `defer cancel()`,把 ctx 传给所有驱动调用;`context.Background()` 无截止时间,慢查询会无限阻塞。
- 批量写入:不要每条记录一个事务,改用 `UNWIND $items AS item MERGE (n:Node {id: item.id}) SET n += item` 在单个事务中批量处理,参数为 `map[string]any{"items": items}`。
- 优先用 v6+ 泛型辅助函数:`neo4j.GetRecordValue[T](record, "name")` 一次完成提取与转换(返回 isNil、err);`neo4j.CollectTWithContext(ctx, result, func(record *neo4j.Record) (Person, error) {...})` 把所有记录映射为切片;`neo4j.SingleTWithContext(ctx, result, ...)` 期望恰好一条记录(0 或 2+ 报错);`neo4j.GetProperty[string](node, "name")` 从 Node 或 Relationship 取类型化属性。
- 空间类型:2D 笛卡尔 `neo4j.Point2D{X, Y, SpatialRefId: 7203}`,3D 笛卡尔 `neo4j.Point3D{X, Y, Z, SpatialRefId: 9157}`,2D WGS-84 `SpatialRefId: 4326`,3D WGS-84 `SpatialRefId: 4979`;作为参数传入查询;读取时断言 `raw.(neo4j.Point2D)` 或 `Point3D`;`point.distance($p1, $p2)` 仅限相同 SRID。
- 始终指定数据库:`neo4j.ExecuteQueryWithDatabase("neo4j")` 或 `neo4j.SessionConfig{DatabaseName: "neo4j"}`,省略会每次调用多一次网络往返解析 home 数据库。
- 因果一致性:`ExecuteQuery` 自动管理书签,顺序调用无需处理;跨会话(并行 worker)需显式合并书签,见 references/repository-pattern.md。
- 常见错误修复:启动时 `ConnectivityError` 检查 URI 方案(Aura 用 `neo4j+s://`)、凭据、7687 端口;运行中 `ConnectivityError` 增大 `MaxConnectionPoolSize` 并检查会话泄漏;类型断言 panic 改用 `GetRecordValue[T]` 或先检查 `ok`;循环后 `res.Err()` 非 nil 处理错误并重跑事务;回调被意外重试把副作用移出回调;context deadline exceeded 用 `context.WithTimeout`;查询正确但 0 结果检查 `DatabaseName`;`CALL IN TRANSACTIONS` 失败改用 `session.Run()`。
- 按需加载参考文档:references/advanced-config.md(连接池调优、自定义地址解析器、通知配置、Bolt 日志、认证选项、URI 方案表、废弃的 result-summary 访问器 `Profile()`→`QueryProfile()`);references/repository-pattern.md(仓储包装模式、跨会话因果一致性书签)。
- 需要时用 WebFetch 查阅:Go 驱动手册 `https://neo4j.com/docs/go-manual/current/`,API 参考 `https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v6/neo4j`。
- 完成清单:启动时创建一个驱动并跨 goroutine 共享、`defer driver.Close(ctx)`;启动时调用 `driver.VerifyConnectivity(ctx)`;所有 `SessionConfig`/`ExecuteQueryWithDatabase` 设置 `DatabaseName`;生产查询用 `context.WithTimeout`;用 `map[string]any` 参数不做字符串插值;只读 `ExecuteQuery` 加 `ExecuteQueryWithReadersRouting()`;惰性循环后检查 `res.Err()`;类型断言有保护(用 `GetRecordValue[T]` 或检查 `ok`);托管事务回调无副作用;`CALL IN TRANSACTIONS`/自动提交查询用 `session.Run()`;会话用 `defer session.Close(ctx)` 关闭。
适用边界 · 注意事项
- 编写或优化 Cypher 查询时——应使用 neo4j-cypher-skill。
- 执行 v5→v6 驱动版本迁移步骤时——应使用 neo4j-migration-skill。