查询数据库

一旦您连接到数据库,您就可以使用Cypher和函数ExecuteQuery()运行查询。

ExecuteQuery() 函数是随驱动程序 5.8 版本引入的。
对于早期版本的查询,请使用会话和事务

写入数据库

要创建表示名为AliceDavid的两个人物节点,并在它们之间建立KNOWS关系,请使用 Cypher 子句CREATE

创建两个节点和一条关系
result, err := neo4j.ExecuteQuery(ctx, driver, `
    CREATE (a:Person {name: $name})  (1)
    CREATE (b:Person {name: $friendName})
    CREATE (a)-[:KNOWS]->(b)
    `,
    map[string]any{  (2)
        "name": "Alice",
        "friendName": "David",
    }, neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))  (3)
if err != nil {
    panic(err)
}

summary := result.Summary  (4)
fmt.Printf("Created %v nodes in %+v.\n",
    summary.Counters().NodesCreated(),
    summary.ResultAvailableAfter())
1 Cypher 查询
2 一个查询参数映射
3 运行查询的数据库
4 服务器返回的执行摘要

从数据库读取

要从数据库中检索信息,请使用 Cypher 子句MATCH

检索所有喜欢其他PersonPerson节点
result, err := neo4j.ExecuteQuery(ctx, driver, `
    MATCH (p:Person)-[:KNOWS]->(:Person)
    RETURN p.name AS name
    `,
    nil,
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
    panic(err)
}

// Loop through results and do something with them
for _, record := range result.Records {  (1)
    name, _ := record.Get("name")  // .Get() 2nd return is whether key is present
    fmt.Println(name)
    // or
    // fmt.Println(record.AsMap())  // get Record as a map
}

// Summary information  (2)
fmt.Printf("The query `%v` returned %v records in %+v.\n",
    result.Summary.Query().Text(), len(result.Records),
    result.Summary.ResultAvailableAfter())
1 result.Records 包含作为 Record 对象数组的结果
2 result.Summary 包含服务器返回的执行摘要
当访问记录内容时,其所有属性都为any类型。这意味着如果您想使用在此类类型上定义的方法/功能,则必须将它们转换为相关的 Go 类型。例如,如果来自数据库的name属性是字符串,record.AsMap()["name"][1] 将在编译时导致无效操作错误。要使其工作,请在将其用作字符串之前将值转换为字符串:name := record.AsMap()["name"].(string),然后使用name[1]

更新数据库

要在数据库中更新节点信息,请使用 Cypher 子句MATCHSET

更新节点Alice以添加age属性
result, err := neo4j.ExecuteQuery(ctx, driver, `
    MATCH (p:Person {name: $name})
    SET p.age = $age
    `, map[string]any{
        "name": "Alice",
        "age": 42,
    }, neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
    panic(err)
}
fmt.Println("Query updated the database?",
    result.Summary.Counters().ContainsUpdates())

要创建新关系,并将其链接到两个已存在的节点,请结合使用 Cypher 子句MATCHCREATE

AliceBob之间创建:KNOWS关系
result, err := neo4j.ExecuteQuery(ctx, driver, `
    MATCH (alice:Person {name: $name})  (1)
    MATCH (bob:Person {name: $friend})  (2)
    CREATE (alice)-[:KNOWS]->(bob)  (3)
    `, map[string]any{
        "name": "Alice",
        "friend": "Bob",
    }, neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
    panic(err)
}
fmt.Println("Query updated the database?",
    result.Summary.Counters().ContainsUpdates())
1 检索名为Alice的人物节点并将其绑定到变量alice
2 检索名为Bob的人物节点并将其绑定到变量bob
3 创建一条从绑定到alice的节点发出的新:KNOWS关系,并将其连接到名为BobPerson节点

从数据库删除

要删除节点及其附加的任何关系,请使用 Cypher 子句DETACH DELETE

删除Alice节点及其所有关系
// This does not delete _only_ p, but also all its relationships!
result, err := neo4j.ExecuteQuery(ctx, driver, `
    MATCH (p:Person {name: $name})
    DETACH DELETE p
    `, map[string]any{
        "name": "Alice",
    }, neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
    panic(err)
}
fmt.Println("Query updated the database?",
    result.Summary.Counters().ContainsUpdates())

查询参数

请勿将参数直接硬编码或连接到查询中。相反,请始终使用占位符并指定Cypher 参数,如前面的示例所示。这是为了

  1. 性能优势:Neo4j 会编译和缓存查询,但只有在查询结构不变的情况下才能这样做;

  2. 安全原因:请参阅防范 Cypher 注入

查询参数应被分组到一个映射中,并作为第二个参数传递给ExecuteQuery()。如果查询没有参数,您可以传递nil而不是空映射。

parameters := map[string]any{
    "name": "Alice",
    "age": 42,
}
neo4j.ExecuteQuery(ctx, driver,
    "MERGE (:Person {name: $name, age: $age})",
    parameters,
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))
在某些情况下,您的查询结构可能会阻止在所有部分中使用参数。对于这些罕见的用例,请参阅属性键、关系类型和标签中的动态值

查询配置

您可以提供其他配置参数来更改ExecuteQuery()的默认行为。这些参数以任意数量的回调形式从第 4 个函数参数开始提供。

数据库选择

建议**始终使用neo4j.ExecuteQueryWithDatabase("<dbName>")回调显式指定数据库**,即使在单数据库实例上也是如此。这使得驱动程序能够更高效地工作,因为它节省了到服务器解析主数据库的网络往返时间。如果没有给定数据库,则使用用户的主数据库

neo4j.ExecuteQuery(ctx, driver,
    "MATCH (p:Person) RETURN p.name",
    nil,
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))
通过配置方法指定数据库优于使用USE Cypher 子句。如果服务器在集群上运行,则包含USE的查询需要启用服务器端路由。查询执行也可能需要更长时间,因为它们可能无法在第一次尝试时到达正确的集群成员,并且需要路由到包含所请求数据库的集群成员。

请求路由

在集群环境中,所有查询默认都指向领导节点。为了提高读取查询的性能,您可以使用回调neo4j.ExecuteQueryWithReadersRouting()将查询路由到读取节点。

neo4j.ExecuteQuery(ctx, driver,
    "MATCH (p:Person) RETURN p.name",
    nil,
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"),
    neo4j.ExecuteQueryWithReadersRouting())

尽管在读取模式下执行写入查询很可能会导致运行时错误,但**您不应将其用于访问控制。**两种模式之间的区别在于,读取事务将被路由到集群的任何节点,而写入事务则指向主节点。换句话说,不能保证以读取模式提交的写入查询会被拒绝。

以不同用户身份运行查询

您可以使用配置回调neo4j.ExecuteQueryWithAuthToken()以不同用户身份执行查询。在查询级别切换用户比创建新的DriverWithContext对象更经济。然后,查询将在给定用户的安全上下文(即主数据库、权限等)中运行。
查询范围的认证要求服务器版本 >= 5.8。

queryAuth := neo4j.BasicAuth("somebodyElse", "theirPassword", "")
neo4j.ExecuteQuery(ctx, driver,
    "MATCH (p:Person) RETURN p.name",
    nil,
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"),
    neo4j.ExecuteQueryWithAuthToken(queryAuth))

回调neo4j.ExecuteQueryWithImpersonatedUser()提供了类似的功能,并且在驱动程序/服务器版本 >= 4.4 中可用。区别在于您不需要知道用户的密码即可模拟他们,但创建Driver的用户需要具有适当的权限

neo4j.ExecuteQuery(ctx, driver,
    "MATCH (p:Person) RETURN p.name",
    nil,
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"),
    neo4j.ExecuteQueryWithImpersonatedUser("<somebodyElse>"))

完整示例

package main

import (
    "fmt"
    "context"
    "github.com/neo4j/neo4j-go-driver/v5/neo4j"
)

func main() {
    ctx := context.Background()

    // Connection to database
    dbUri := "{neo4j-database-uri}"
    dbUser := "{neo4j-username}"
    dbPassword := "{neo4j-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)
    }

    // Prepare data
    people := []map[string]any {
       {"name": "Alice", "age": 42, "friends": []string{"Bob", "Peter", "Anna"},},
       {"name": "Bob", "age": 19,},
       {"name": "Peter", "age": 50,},
       {"name": "Anna", "age": 30,},
    }

    // Create some nodes
    for _, person := range people {
        _, err := neo4j.ExecuteQuery(ctx, driver,
            "MERGE (p:Person {name: $person.name, age: $person.age})",
            map[string]any{
                "person": person,
            }, neo4j.EagerResultTransformer,
            neo4j.ExecuteQueryWithDatabase("neo4j"))
        if err != nil {
            panic(err)
        }
    }

    // Create some relationships
    for _, person := range people {
        if person["friends"] != "" {
            _, err := neo4j.ExecuteQuery(ctx, driver, `
                MATCH (p:Person {name: $person.name})
                UNWIND $person.friends AS friend_name
                MATCH (friend:Person {name: friend_name})
                MERGE (p)-[:KNOWS]->(friend)
                `, map[string]any{
                    "person": person,
                }, neo4j.EagerResultTransformer,
                neo4j.ExecuteQueryWithDatabase("neo4j"))
            if err != nil {
                panic(err)
            }
        }
    }

    // Retrieve Alice's friends who are under 40
    result, err := neo4j.ExecuteQuery(ctx, driver, `
        MATCH (p:Person {name: $name})-[:KNOWS]-(friend:Person)
        WHERE friend.age < $age
        RETURN friend
        `, map[string]any{
            "name": "Alice",
            "age": 40,
        }, neo4j.EagerResultTransformer,
        neo4j.ExecuteQueryWithDatabase("neo4j"))
    if err != nil {
        panic(err)
    }

    // Loop through results and do something with them
    for _, record := range result.Records {
        person, _ := record.Get("friend")
        fmt.Println(person)
        // or
        // fmt.Println(record.AsMap())
    }

    // Summary information
    fmt.Printf("\nThe query `%v` returned %v records in %+v.\n",
        result.Summary.Query().Text(), len(result.Records),
        result.Summary.ResultAvailableAfter())
}

欲了解更多信息,请参阅API 文档 → ExecuteQuery()

术语表

LTS

长期支持 (LTS) 版本是指保证支持多年的版本。Neo4j 4.4 是 LTS 版本,Neo4j 5 也将有一个 LTS 版本。

Aura

Aura 是 Neo4j 的全托管云服务。它提供免费和付费计划。

Cypher

Cypher 是 Neo4j 的图查询语言,允许您从数据库中检索数据。它类似于 SQL,但专用于图。

APOC

Awesome Procedures On Cypher (APOC) 是一个函数库,其中包含许多无法在 Cypher 本身中轻松表达的函数。

Bolt

Bolt 是 Neo4j 实例和驱动程序之间交互使用的协议。它默认监听端口 7687。

ACID

原子性 (Atomicity)、一致性 (Consistency)、隔离性 (Isolation)、持久性 (Durability) (ACID) 是保证数据库事务可靠处理的属性。符合 ACID 的 DBMS 确保数据库中的数据即使在发生故障时也能保持准确和一致。

最终一致性

如果数据库提供所有集群成员将在某个时间点存储最新版本数据的保证,则该数据库是最终一致的。

因果一致性

如果集群的每个成员都以相同的顺序看到读写查询,则数据库是因果一致的。这比最终一致性更强。

NULL

null 标记不是一种类型,而是表示值缺失的占位符。欲了解更多信息,请参阅Cypher → 使用null

事务

事务是一个工作单元,它要么整体提交,要么在失败时回滚。例如银行转账:它涉及多个步骤,但它们必须全部成功或被撤销,以避免资金从一个账户扣除而未添加到另一个账户的情况。

反压

反压是与数据流相反的作用力。它确保客户端不会被超出其处理能力的数据淹没。

事务函数

事务函数是由ExecuteReadExecuteWrite调用执行的回调。驱动程序会在服务器故障时自动重新执行该回调。

DriverWithContext

一个DriverWithContext对象包含与 Neo4j 数据库建立连接所需的详细信息。

© . All rights reserved.