使用 Neo4j 和 Go 构建应用程序
Neo4j Go 驱动程序是通过 Go 应用程序与 Neo4j 实例交互的官方库。
Neo4j 的核心是 Cypher,它是与 Neo4j 数据库交互的查询语言。虽然本指南并不 *要求* 您是经验丰富的 Cypher 查询员,但如果您已经了解一些 Cypher,则可以更轻松地专注于 Go 特定的部分。因此,虽然本指南 *也* 在过程中提供了对 Cypher 的简要介绍,但如果您是第一次接触,请考虑查看 入门 → Cypher,以更详细地了解图数据库建模和查询。然后,您可以在遵循本指南开发 Go 应用程序时应用这些知识。
连接到数据库
通过创建 DriverWithContext 对象并提供 URL 和身份验证令牌来连接到数据库。获得 DriverWithContext
实例后,使用 .VerifyConnectivity()
方法确保可以建立工作连接。
package main
import (
"fmt"
"context"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
func main() {
ctx := context.Background()
// URI examples: "neo4j://localhost", "neo4j+s://xxx.databases.neo4j.io"
dbUri := "<URI for Neo4j database>"
dbUser := "<Username>"
dbPassword := "<Password>"
driver, err := neo4j.NewDriverWithContext(
dbUri,
neo4j.BasicAuth(dbUser, dbPassword, ""))
defer driver.Close(ctx)
err = driver.VerifyConnectivity(ctx)
if err != nil {
panic(err)
}
fmt.Println("Connection established.")
}
查询数据库
使用函数 ExecuteQuery()
执行 Cypher 语句。不要硬编码或连接参数:使用占位符并将参数指定为关键字参数。
// Get the name of all 42 year-olds
result, _ := neo4j.ExecuteQuery(ctx, driver,
"MATCH (p:Person {age: $age}) RETURN p.name AS name",
map[string]any{
"age": "42",
}, neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"))
// Loop through results and do something with them
for _, record := range result.Records {
fmt.Println(record.AsMap())
}
// Summary information
fmt.Printf("The query `%v` returned %v records in %+v.\n",
result.Summary.Query().Text(), len(result.Records),
result.Summary.ResultAvailableAfter())
运行您自己的事务
对于更高级的使用案例,您可以运行 事务。使用方法 Session.ExecuteRead()
和 Session.ExecuteWrite()
来运行托管事务。
package main
import (
"fmt"
"context"
"strconv"
"errors"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
func main() {
ctx := context.Background()
var employeeThreshold int64 = 10 // Neo4j's integer maps to Go's int64
// Connection to database
dbUri := "<URI for Neo4j database>"
dbUser := "<Username>"
dbPassword := "<Password>"
driver, err := neo4j.NewDriverWithContext(
dbUri,
neo4j.BasicAuth(dbUser, dbPassword, ""))
if err != nil {
panic(err)
}
defer driver.Close(ctx)
err = driver.VerifyConnectivity(ctx)
if err != nil {
panic(err)
}
session := driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: "neo4j"})
defer session.Close(ctx)
// Create 100 people and assign them to various organizations
for i := 0; i < 100; i++ {
name := "Thor" + strconv.Itoa(i)
orgId, err := session.ExecuteWrite(ctx,
func(tx neo4j.ManagedTransaction) (any, error) {
var orgId string
// Create new Person node with given name, if not exists already
_, err := tx.Run(
ctx,
"MERGE (p:Person {name: $name})",
map[string]any{
"name": name,
})
if err != nil {
return nil, err
}
// Obtain most recent organization ID and the number of people linked to it
result, err := tx.Run(
ctx, `
MATCH (o:Organization)
RETURN o.id AS id, COUNT{(p:Person)-[r:WORKS_FOR]->(o)} AS employeesN
ORDER BY o.createdDate DESC
LIMIT 1
`, nil)
if err != nil {
return nil, err
}
org, err := result.Single(ctx)
// If no organization exists, create one and add Person to it
if org == nil {
orgId, _ = createOrganization(ctx, tx)
fmt.Println("No orgs available, created", orgId)
err = addPersonToOrganization(ctx, tx, name, orgId)
if err != nil {
return nil, errors.New("Failed to add person to new org")
// Transaction will roll back
// -> not even Person and/or Organization is created!
}
} else {
orgId = org.AsMap()["id"].(string)
if employeesN := org.AsMap()["employeesN"].(int64);
employeesN == 0 {
return nil, errors.New("Most recent organization is empty")
// Transaction will roll back
// -> not even Person is created!
}
// If org does not have too many employees, add this Person to it
if employeesN := org.AsMap()["employeesN"].(int64);
employeesN < employeeThreshold {
err = addPersonToOrganization(ctx, tx, name, orgId)
if err != nil {
return nil, err
// Transaction will roll back
// -> not even Person is created!
}
// Otherwise, create a new Organization and link Person to it
} else {
orgId, err = createOrganization(ctx, tx)
if err != nil {
return nil, err
// Transaction will roll back
// -> not even Person is created!
}
fmt.Println("Latest org is full, created", orgId)
err = addPersonToOrganization(ctx, tx, name, orgId)
if err != nil {
return nil, err
// Transaction will roll back
// -> not even Person and/or Organization is created!
}
}
}
// Return the Organization ID to which the new Person ends up in
return orgId, nil
})
if err != nil {
fmt.Println(err)
} else {
fmt.Println("User", name, "added to organization", orgId)
}
}
}
func createOrganization(ctx context.Context, tx neo4j.ManagedTransaction) (string, error) {
result, err := tx.Run(
ctx, `
CREATE (o:Organization {id: randomuuid(), createdDate: datetime()})
RETURN o.id AS id
`, nil)
if err != nil {
return "", err
}
org, err := result.Single(ctx)
if err != nil {
return "", err
}
orgId, _ := org.AsMap()["id"]
return orgId.(string), err
}
func addPersonToOrganization(ctx context.Context, tx neo4j.ManagedTransaction, personName string, orgId string) (error) {
_, err := tx.Run(
ctx, `
MATCH (o:Organization {id: $orgId})
MATCH (p:Person {name: $name})
MERGE (p)-[:WORKS_FOR]->(o)
`, map[string]any{
"orgId": orgId,
"name": personName,
})
return err
}
关闭连接和会话
在所有 DriverWithContext
和 SessionWithContext
实例上调用 .close()
方法以释放它们持有的任何资源。最佳做法是在创建新对象后立即使用 defer
关键字调用这些方法。
driver, err := neo4j.NewDriverWithContext(dbUri, neo4j.BasicAuth(dbUser, dbPassword, ""))
defer driver.Close(ctx)
session := driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: "neo4j"})
defer session.Close(ctx)
API 文档
有关驱动程序功能的深入信息,请查看 API 文档.
词汇表
- LTS
-
长期支持 版本是保证支持数年的一种版本。Neo4j 4.4 是 LTS,Neo4j 5 也将有一个 LTS 版本。
- Aura
-
Aura 是 Neo4j 的完全托管云服务。它提供免费和付费计划。
- Cypher
-
Cypher 是 Neo4j 的图查询语言,允许您从数据库中检索数据。它类似于 SQL,但适用于图。
- APOC
-
Cypher 上的强大程序 (APOC) 是一个包含 (许多) 函数的库,这些函数无法轻松地在 Cypher 本身中表达。
- Bolt
-
Bolt 是用于 Neo4j 实例和驱动程序之间交互的协议。默认情况下,它监听端口 7687。
- ACID
-
原子性、一致性、隔离性、持久性 (ACID) 是保证数据库事务可靠处理的属性。符合 ACID 的 DBMS 确保数据库中的数据即使在发生故障的情况下也能保持准确和一致。
- 最终一致性
-
如果数据库保证所有集群成员 *在某个时间点* 将存储数据的最新版本,则该数据库最终一致。
- 因果一致性
-
如果每个集群成员都以相同的顺序看到读写查询,则数据库是因果一致的。这比 *最终一致性* 更强。
- NULL
-
空标记不是类型,而是值缺失的占位符。有关更多信息,请参阅 Cypher → 使用
null
. - 事务
-
事务是工作单元,要么 *完整提交*,要么在发生故障时 *回滚*。例如银行转账:它包含多个步骤,但它们必须 *全部* 成功或被恢复,以避免从一个帐户中扣除资金,而未将其添加到另一个帐户中。
- 背压
-
背压是反对数据流动的力。它确保客户端不会被超出其处理能力的数据淹没。
- 事务函数
-
事务函数是
ExecuteRead
或ExecuteWrite
调用执行的回调。如果服务器发生故障,驱动程序会自动重新执行回调。 - DriverWithContext
-
DriverWithContext
对象包含建立与 Neo4j 数据库连接所需的详细信息。