mcpField
N
neo4j-driver-go-skill

neo4j-driver-go-skill

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

关于 neo4j-driver-go-skill

面向使用 Go 连接 Neo4j 的开发者的技能指南,涵盖驱动、查询、事务、结果处理和错误排查,并说明常见性能与安全实践。

主要功能

  1. 01配置 Neo4j Go Driver v6
  2. 02选择查询与事务 API
  3. 03处理结果类型和错误
  4. 04管理连接、会话与上下文
  5. 05批量写入及查询参数实践

适用场景

  1. 01在 Go 应用中连接 Neo4j
  2. 02排查连接或结果迭代错误
  3. 03为大型结果集选择流式处理
  4. 04编写可重试的事务逻辑

安装

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

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

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

包含的文件

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

README.md
references/
advanced-config.md
repository-pattern.md

详细介绍

适用场景

  • 编写连接 Neo4j 的 Go 代码
  • 设置 neo4j.NewDriver()、ExecuteQuery() 或会话/事务模式
  • 调试连接错误、结果迭代、类型断言和因果一致性

不适用场景

  • 编写/优化 Cypher → neo4j-cypher-skill
  • v5→v6 迁移步骤 → neo4j-migration-skill

安装

go get github.com/neo4j/neo4j-go-driver/v6

导入:github.com/neo4j/neo4j-go-driver/v6/neo4j

v5→v6 重命名(已弃用的别名仍可编译,须在 v7 前移除):

v5v6
neo4j.NewDriverWithContext(...)neo4j.NewDriver(...)
neo4j.DriverWithContextneo4j.Driver

环境变量

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
}

在开发环境中,使用 godotenv 加载 .env:godotenv.Load()。将 .env 添加到 .gitignore。


驱动生命周期

每个应用使用一个 Driver。它支持 goroutine 安全并发,使用连接池,创建成本较高。

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)

❌ 切勿为每个请求创建驱动。在启动时创建一次,并在 goroutine 之间共享。

URI 方案:neo4j+s://(Aura/TLS+路由)、neo4j://(明文+路由)、bolt+s://(TLS+单实例)、bolt://(明文+单实例)。


选择合适的 API

API适用场景自动重试延迟加载结果
neo4j.ExecuteQuery()大多数查询——简单的默认选择✅❌ 即时加载
session.ExecuteRead/Write()大型结果集/流式处理✅✅
session.BeginTransaction()跨多个函数/需要外部协调❌✅
session.Run()仅用于 CALL IN TRANSACTIONS/自动提交❌✅

CALL { … } IN TRANSACTIONS 和 USING PERIODIC COMMIT 会管理自己的事务——请使用 session.Run()。它们在托管事务中会失败。


ExecuteQuery(推荐的默认方式)

自动管理会话、事务、重试和书签。

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

关键选项:

neo4j.ExecuteQueryWithDatabase("mydb")          // required for performance
neo4j.ExecuteQueryWithReadersRouting()           // route reads to replicas
neo4j.ExecuteQueryWithImpersonatedUser("jane")  // impersonate
neo4j.ExecuteQueryWithoutBookmarkManager()       // opt out of causal consistency

❌ 切勿将用户输入拼接到查询字符串中。始终使用 map[string]any 参数。

结构体参数 [v6.2.0+,对象映射预览版]——params 中的结构体(或指针)会作为 Cypher 映射发送;Session.Run、ManagedTransaction.Run 和 ExplicitTransaction.Run 也遵循相同规则:

type Person struct {
    Name     string `neo4j:"name"`
    Age      int    `neo4j:"age,omitempty"`  // zero value dropped
    Internal string `neo4j:"-"`              // never sent
}
_, err := neo4j.ExecuteQuery(ctx, driver,
    "MERGE (p:Person {name: $person.name}) SET p += $person",
    map[string]any{"person": Person{Name: "Alice", Age: 30}},
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"),
)

托管事务(基于会话)

用于延迟流式处理(大型结果集)或在回调级别进行控制。

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()
    },
)

❌ 不要在回调中执行副作用——遇到瞬时故障时会重试。 ExecuteRead → 副本。ExecuteWrite → 集群主节点。


显式事务

用于事务操作跨多个函数,或需要外部协调的场景。

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)

❌ 不会自动重试。由调用方处理重试。除非需要显式控制,否则优先使用托管事务。


错误处理

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)
}

辅助函数:

neo4j.IsNeo4jError(err)                // server-side Cypher/database error
neo4j.IsTransactionExecutionLimit(err) // managed tx retries exhausted

在托管事务回调中:返回错误 → 如果是瞬时错误,驱动会重试。 启动时出现 ConnectivityError:检查 URI 方案、凭据和防火墙。


数据类型

CypherGo
Integerint64
Floatfloat64
Stringstring
Booleanbool
List[]any
Mapmap[string]any
Nodeneo4j.Node
Relationshipneo4j.Relationship
Pathneo4j.Path
Dateneo4j.Date
DateTimeneo4j.Time
Durationneo4j.Duration
UUIDneo4j.UUID(= dbtype.UUID,[16]byte,RFC 9562)——v6.2.0+、Bolt 6.1;使用 dbtype.ParseUUID(s) 解析,使用 .String() 转为字符串
nullnil
// 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

❌ 在对 record.Get() 的结果进行类型断言前,务必检查 ok——键缺失时会触发 panic。 ❌ 延迟执行 for res.Next(ctx) 循环后,务必检查 res.Err()。


关键模式

Context——始终传递
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
defer cancel()
// pass ctx to all driver calls

context.Background() 没有截止时间——慢查询会无限期阻塞。

批量写入
// 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"),
)
通用辅助函数(v6+)

优先使用类型安全的辅助函数,而不是手动断言:

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

结果映射 [v6.3.0+,对象映射预览版] — 将记录映射到带有 neo4j 标签的结构体(与结构体参数使用相同的标签):

type Person struct {
    Name string `neo4j:"name"`
    Age  int    `neo4j:"age"`   // INTEGER converted; lossy conversion → error, not truncation
}

// EagerResult from ExecuteQuery
res, err := neo4j.ExecuteQuery(ctx, driver,
    "MATCH (p:Person) RETURN p.name AS name, p.age AS age LIMIT 25",
    nil, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase("neo4j"))
people, err := neo4j.CollectRecordsAs[Person](res.Records)

// Streaming Result inside a transaction
all, err := neo4j.CollectAs[Person](ctx, result)      // all records
person, err := neo4j.SingleAs[Person](ctx, result)    // exactly one record
p, err := neo4j.As[Person](record)                    // single record

映射规则:如果只有一列且包含节点、关系或映射,则将其属性映射到字段;否则按名称映射列。缺失值/null → 零值;多余的列将被忽略。

空间类型
// 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))
始终指定数据库
neo4j.ExecuteQueryWithDatabase("neo4j")    // in ExecuteQuery
neo4j.SessionConfig{DatabaseName: "neo4j"} // in sessions

省略此项会导致每次调用都产生一次网络往返,以解析默认数据库。

因果一致性

ExecuteQuery 会自动管理书签——顺序调用无需执行任何操作。 跨会话(并行工作进程):显式合并书签 — 参见 references/repository-pattern.md。


常见错误

错误 / 症状原因解决方法
启动时出现 ConnectivityErrorURI 错误 / TLS 不匹配 / 防火墙检查协议(Aura 使用 neo4j+s://)、凭据和端口 7687
运行期间出现 ConnectivityError连接池耗尽增大 MaxConnectionPoolSize;检查是否有未关闭的会话
类型断言时发生 Panicrecord.Get() 返回了 nil/错误类型使用 neo4j.GetRecordValue[T]() 或先检查 ok
循环结束后 res.Err() 非 nil流处理中发生网络错误处理错误;重新运行事务
回调意外重试在托管事务中执行了副作用将副作用移到回调之外
上下文截止时间已超出上下文未设置超时使用 context.WithTimeout
返回 0 条结果,查询看起来正确DatabaseName 错误始终在配置中设置 DatabaseName
CALL IN TRANSACTIONS 失败在托管事务中运行使用 session.Run()(自动提交)

参考资料

按需加载:

  • references/advanced-config.md — 连接池调优、自定义地址解析器、通知配置、Bolt 日志记录、身份验证选项、URI 协议表、已弃用的结果摘要访问器(Profile() → QueryProfile())
  • references/repository-pattern.md — 仓储封装模式、使用书签实现跨会话因果一致性

WebFetch

需求URL
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()
  • 在惰性 for result.Next(ctx) 循环后检查 res.Err()
  • 为类型断言添加保护(使用 GetRecordValue[T] 或检查 ok)
  • 不在托管事务回调中执行副作用
  • 对 CALL IN TRANSACTIONS / 自动提交查询使用 session.Run()
  • 使用 defer session.Close(ctx) 关闭会话

评论

支持 Markdown