查询数据库

连接到数据库后,您可以通过方法 Driver.executableQuery() 执行 Cypher 查询。

Driver.executableQuery() 是驱动程序 5.8 版本中引入的。
对于早期版本的查询,请使用会话和事务

写入数据库

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

创建两个节点和一条关系
// import java.util.Map;
// import java.util.concurrent.TimeUnit;
// import org.neo4j.driver.QueryConfig;

var result = driver.executableQuery("""
    CREATE (a:Person {name: $name})  (1)
    CREATE (b:Person {name: $friendName})
    CREATE (a)-[:KNOWS]->(b)
    """)
    .withParameters(Map.of("name", "Alice", "friendName", "David"))  (2)
    .withConfig(QueryConfig.builder().withDatabase("neo4j").build())  (3)
    .execute();
var summary = result.summary();  (4)
System.out.printf("Created %d records in %d ms.%n",
    summary.counters().nodesCreated(),
    summary.resultAvailableAfter(TimeUnit.MILLISECONDS));
1 Cypher 查询
2 查询参数映射
3 运行查询的数据库
4 服务器返回的执行摘要

从数据库读取

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

检索所有喜欢其他 PersonPerson 节点
// import java.util.concurrent.TimeUnit;
// import org.neo4j.driver.QueryConfig;

var result = driver.executableQuery("""
    MATCH (p:Person)-[:KNOWS]->(:Person)
    RETURN p.name AS name
    """)
    .withConfig(QueryConfig.builder().withDatabase("neo4j").build())
    .execute();

// Loop through results and do something with them
var records = result.records();  (1)
records.forEach(r -> {
    System.out.println(r);  // or r.get("name").asString()
});

// Summary information
var summary = result.summary();  (2)
System.out.printf("The query %s returned %d records in %d ms.%n",
    summary.query(), records.size(),
    summary.resultAvailableAfter(TimeUnit.MILLISECONDS));
1 records 包含作为 Record 对象列表的结果
2 summary 包含服务器返回的执行摘要

Record 对象中的属性嵌入在 Value 对象中。要提取并将它们转换为相应的 Java 类型,请使用 .as<type>()(例如 .asString()asInt() 等)。例如,如果来自数据库的 name 属性是字符串,record.get("name").asString() 将生成 String 类型的属性值。

更多信息,请参阅数据类型和到 Cypher 类型的映射

更新数据库

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

更新节点 Alice 以添加 age 属性
// import java.util.Map;
// import org.neo4j.driver.QueryConfig;

var result = driver.executableQuery("""
    MATCH (p:Person {name: $name})
    SET p.age = $age
    """)
    .withConfig(QueryConfig.builder().withDatabase("neo4j").build())
    .withParameters(Map.of("name", "Alice", "age", 42))
    .execute();
var summary = result.summary();
System.out.println("Query updated the database?");
System.out.println(summary.counters().containsUpdates());

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

AliceBob 之间创建 :KNOWS 关系
// import java.util.Map;
// import org.neo4j.driver.QueryConfig;

var result = driver.executableQuery("""
    MATCH (alice:Person {name: $name})  (1)
    MATCH (bob:Person {name: $friend})  (2)
    CREATE (alice)-[:KNOWS]->(bob)  (3)
    """)
    .withConfig(QueryConfig.builder().withDatabase("neo4j").build())
    .withParameters(Map.of("name", "Alice", "friend", "Bob"))
    .execute();
var summary = result.summary();
System.out.println("Query updated the database?");
System.out.println(summary.counters().containsUpdates());
1 检索名为 Alice 的人物节点并将其绑定到变量 alice
2 检索名为 Bob 的人物节点并将其绑定到变量 bob
3 创建从绑定到 alice 的节点发出的新 :KNOWS 关系,并将名为 BobPerson 节点附加到其上

从数据库删除

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

删除 Alice 节点及其所有关系
// import java.util.Map;
// import org.neo4j.driver.QueryConfig;

// This does not delete _only_ p, but also all its relationships!
var result = driver.executableQuery("""
    MATCH (p:Person {name: $name})
    DETACH DELETE p
    """)
    .withConfig(QueryConfig.builder().withDatabase("neo4j").build())
    .withParameters(Map.of("name", "Alice"))
    .execute();
var summary = result.summary();
System.out.println("Query updated the database?");
System.out.println(summary.counters().containsUpdates());

查询参数

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

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

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

您可以通过 .withParameters() 方法以映射形式提供查询参数。

var result = driver.executableQuery("MATCH (p:Person {name: $name}) RETURN p")
    .withParameters(Map.of("name", "Alice"))
    .withConfig(QueryConfig.builder().withDatabase("neo4j").build())
    .execute();
在某些情况下,您的查询结构可能无法在所有部分中使用参数。对于这些罕见的用例,请参阅属性键、关系类型和标签中的动态值

错误处理

查询运行可能会因多种原因失败,并引发不同的异常。其中一些是由于 Cypher 语法错误、权限问题或其他形式的配置错误/误用引起的。如何处理这些异常的选择取决于您的应用程序:无论是您想要防御性处理(例如检查是否有要处理的记录以避免 NoSuchRecordException),还是您想要捕获并处理出现的异常。

如果故障被认为是瞬态的(例如由于服务器暂时不可用),驱动程序会自动重试运行失败的查询。如果操作在多次尝试后仍然失败,则会引发异常。

查询配置

您可以提供其他配置参数来更改 .executableQuery() 的默认行为。您可以通过 .withConfig() 方法进行此操作,该方法接受一个 QueryConfig 对象。

数据库选择

建议始终使用 .withDatabase("<dbName>") 方法显式指定数据库,即使在单数据库实例上也是如此。这使驱动程序能够更高效地工作,因为它省去了到服务器解析主数据库的网络往返。如果未指定数据库,则使用用户的主数据库

// import org.neo4j.driver.QueryConfig;

var result = driver.executableQuery("MATCH (p:Person) RETURN p.name")
    .withConfig(QueryConfig.builder().withDatabase("neo4j").build())
    .execute();
通过配置方法指定数据库优于使用 USE Cypher 子句。如果服务器在集群上运行,带有 USE 的查询需要启用服务器端路由。查询执行时间也可能更长,因为它们在首次尝试时可能无法到达正确的集群成员,需要路由到包含所请求数据库的成员。

请求路由

在集群环境中,所有查询默认都定向到主节点。为了提高读取查询的性能,您可以使用 .withRouting(RoutingControl.READ) 方法将查询路由到读取节点。

// import org.neo4j.driver.QueryConfig;
// import org.neo4j.driver.RoutingControl;

var result = driver.executableQuery("MATCH (p:Person) RETURN p.name")
    .withConfig(QueryConfig.builder()
        .withDatabase("neo4j")
        .withRouting(RoutingControl.READ)
        .build())
    .execute();

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

以不同用户身份运行查询

您可以使用 .withAuthToken() 方法以不同用户身份执行查询。在查询级别切换用户比创建新的 Driver 对象更经济高效。然后,查询将在给定用户的安全上下文中运行(即,主数据库、权限等)。
查询范围的身份验证需要服务器版本 >= 5.8。

// import org.neo4j.driver.AuthTokens;
// import org.neo4j.driver.QueryConfig;

var authToken = AuthTokens.basic("somebodyElse", "theirPassword");
var result = driver.executableQuery("MATCH (p:Person) RETURN p.name")
    .withConfig(QueryConfig.builder()
        .withDatabase("neo4j")
        .withAuthToken(authToken)
        .build())
    .execute();

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

// import org.neo4j.driver.QueryConfig;

var result = driver.executableQuery("MATCH (p:Person) RETURN p.name")
    .withConfig(QueryConfig.builder()
        .withDatabase("neo4j")
        .withImpersonatedUser("somebodyElse")
        .build())
    .execute();

完整示例

package demo;

import java.util.Map;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.Record;
import org.neo4j.driver.QueryConfig;
import org.neo4j.driver.RoutingControl;

public class App {

    public static void main(String... args) {
        final String dbUri = "{neo4j-database-uri}";
        final String dbUser = "{neo4j-username}";
        final String dbPassword = "{neo4j-password}";

        try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword))) {

            List<Map> people = List.of(
                Map.of("name", "Alice", "age", 42, "friends", List.of("Bob", "Peter", "Anna")),
                Map.of("name", "Bob", "age", 19),
                Map.of("name", "Peter", "age", 50),
                Map.of("name", "Anna", "age", 30)
            );

            try {

                //Create some nodes
                people.forEach(person -> {
                    var result = driver.executableQuery("CREATE (p:Person {name: $person.name, age: $person.age})")
                        .withConfig(QueryConfig.builder().withDatabase("neo4j").build())
                        .withParameters(Map.of("person", person))
                        .execute();
                });

                // Create some relationships
                people.forEach(person -> {
                    if(person.containsKey("friends")) {
                        var result = driver.executableQuery("""
                            MATCH (p:Person {name: $person.name})
                            UNWIND $person.friends AS friend_name
                            MATCH (friend:Person {name: friend_name})
                            CREATE (p)-[:KNOWS]->(friend)
                             """)
                            .withConfig(QueryConfig.builder().withDatabase("neo4j").build())
                            .withParameters(Map.of("person", person))
                            .execute();
                    }
                });

                // Retrieve Alice's friends who are under 40
                var result = driver.executableQuery("""
                    MATCH (p:Person {name: $name})-[:KNOWS]-(friend:Person)
                    WHERE friend.age < $age
                    RETURN friend
                     """)
                    .withConfig(QueryConfig.builder()
                        .withDatabase("neo4j")
                        .withRouting(RoutingControl.READ)
                        .build())
                    .withParameters(Map.of("name", "Alice", "age", 40))
                    .execute();

                // Loop through results and do something with them
                result.records().forEach(r -> {
                    System.out.println(r);
                });

                // Summary information
                System.out.printf("The query %s returned %d records in %d ms.%n",
                    result.summary().query(), result.records().size(),
                    result.summary().resultAvailableAfter(TimeUnit.MILLISECONDS));

            } catch (Exception e) {
                System.out.println(e.getMessage());
                System.exit(1);
            }
        }
    }
}

更多信息请参阅 API 文档 → Driver.executableQuery()

词汇表

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

原子性、一致性、隔离性、持久性 (ACID) 是确保数据库事务可靠处理的属性。符合 ACID 的数据库管理系统确保即使发生故障,数据库中的数据也能保持准确和一致。

最终一致性

如果数据库保证所有集群成员都将在某个时间点存储最新版本的数据,那么它就是最终一致的。

因果一致性

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

NULL

空标记不是一种类型,而是表示值缺失的占位符。更多信息,请参阅 Cypher → 处理 null

事务

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

背压

背压是阻止数据流动的力。它确保客户端不会被超出其处理能力的数据量所淹没。

事务函数

事务函数是由 executeReadexecuteWrite 调用执行的回调。在服务器故障的情况下,驱动程序会自动重新执行回调。

驱动程序

一个 Driver 对象保存着与 Neo4j 数据库建立连接所需的详细信息。

© . All rights reserved.