查询数据库

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

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

写入数据库

要创建一个表示名为 Alice 的人的节点,请使用 Cypher 子句 CREATE

创建一个表示名为 Alice 的人的节点
// import java.util.Map;
// import java.util.concurrent.TimeUnit;
// import org.neo4j.driver.QueryConfig;

var result = driver.executableQuery("CREATE (:Person {name: $name})")  (1)
    .withParameters(Map.of("name", "Alice"))  (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

检索所有 Person 节点
// import java.util.concurrent.TimeUnit;
// import org.neo4j.driver.QueryConfig;

var result = driver.executableQuery("MATCH (p: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 创建一个新的 :KNOWS 关系,从绑定到 alice 的节点发出,并将其附加到名为 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>") 方法,即使是在单数据库实例上也是如此。这使驱动程序能够更有效地工作,因为它节省了网络往返服务器以解析主数据库的步骤。如果没有给出数据库,则使用 用户的 home 数据库

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

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

以不同用户身份运行查询

您可以使用方法.withImpersonatedUser("<username>")以不同用户的安全上下文执行查询,指定要模拟的用户名称。要使此方法生效,创建Driver的用户的权限需要满足适当的权限。模拟用户比创建新的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 = "<URI for Neo4j database>";
        final String dbUser = "<Username>";
        final String dbPassword = "<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

Cypher 上的强大过程 (APOC) 是一个包含(许多)函数的库,这些函数不能轻易用 Cypher 本身表达。

Bolt

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

ACID

原子性、一致性、隔离性、持久性 (ACID) 是保证数据库事务可靠处理的属性。符合 ACID 的 DBMS 确保数据库中的数据在出现故障时保持准确性和一致性。

最终一致性

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

因果一致性

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

NULL

空标记不是类型,而是值缺失的占位符。有关更多信息,请参阅Cypher → 使用null.

事务

事务是工作单元,要么全部提交,要么在发生故障时回滚。例如银行转账:它涉及多个步骤,但必须全部成功或被恢复,以避免从一个账户中扣除资金,但没有添加到另一个账户中。

背压

背压是与数据流相反的力量。它确保客户端不会被数据淹没,而数据流速超出了它的处理能力。

事务函数

事务函数是由executeReadexecuteWrite调用执行的回调函数。如果服务器发生故障,驱动程序会自动重新执行回调函数。

Driver

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