使用 Neo4j 和 Java 构建应用程序

Neo4j Java 驱动程序是通过 Java 应用程序与 Neo4j 实例交互的官方库。

Neo4j 的核心是Cypher,它是与 Neo4j 数据库交互的查询语言。虽然本指南并*不*要求您成为经验丰富的 Cypher 查询员,但如果您已经了解一些 Cypher,那么专注于 Java 特定的部分会更容易。因此,虽然本指南也*会*逐步介绍 Cypher,但如果您是第一次接触图数据库建模和查询,请考虑查看入门 → Cypher以获取更详细的演练。然后,您可以在遵循本指南开发 Java 应用程序时应用这些知识。

安装

将 Neo4j Java 驱动程序添加到 Maven 项目的pom.xml中的依赖项列表中

<dependency>
    <groupId>org.neo4j.driver</groupId>
    <artifactId>neo4j-java-driver</artifactId>
    <version>5.26.0</version>
</dependency>

连接到数据库

通过创建Driver对象并提供 URL 和身份验证令牌来连接到数据库。获得Driver实例后,使用.verifyConnectivity()方法确保可以建立工作连接。

package demo;

import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.GraphDatabase;

public class App {

    public static void main(String... args) {

        // URI examples: "neo4j://127.0.0.1", "neo4j+s://xxx.databases.neo4j.io"
        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))) {
            driver.verifyConnectivity();
            System.out.println("Connection established.");
        }
    }
}

查询数据库

使用Driver.executableQuery()方法执行 Cypher 语句。不要硬编码或连接参数:使用占位符并通过.withParameters()方法将参数指定为映射。

// import java.util.Map;
// import org.neo4j.driver.QueryConfig;

// Get all 42-year-olds
var result = driver.executableQuery("MATCH (p:Person {age: $age}) RETURN p.name AS name")
    .withParameters(Map.of("age", 42))
    .withConfig(QueryConfig.builder().withDatabase("neo4j").build())
    .execute();

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

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

运行自己的事务

对于更高级的用例,您可以运行事务。使用Session.executeRead()Session.executeWrite()方法运行托管事务。

一个包含多个查询、客户端逻辑和潜在回滚的事务
package demo;

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

import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.QueryConfig;
import org.neo4j.driver.Record;
import org.neo4j.driver.RoutingControl;
import org.neo4j.driver.SessionConfig;
import org.neo4j.driver.TransactionContext;
import org.neo4j.driver.exceptions.NoSuchRecordException;

public class App {

    // Create & employ 100 people to 10 different organizations
    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))) {
            try (var session = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
                for (int i=0; i<100; i++) {
                    String name = String.format("Thor%d", i);

                    try {
                        String orgId = session.executeWrite(tx -> employPersonTx(tx, name));
                        System.out.printf("User %s added to organization %s.%n", name, orgId);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                }
            }
        }
    }

    static String employPersonTx(TransactionContext tx, String name) {
        final int employeeThreshold = 10;

        // Create new Person node with given name, if not exists already
        tx.run("MERGE (p:Person {name: $name})", Map.of("name", name));

        // Obtain most recent organization ID and the number of people linked to it
        var result = tx.run("""
            MATCH (o:Organization)
            RETURN o.id AS id, COUNT{(p:Person)-[r:WORKS_FOR]->(o)} AS employeesN
            ORDER BY o.createdDate DESC
            LIMIT 1
            """);

        Record org = null;
        String orgId = null;
        int employeesN = 0;
        try {
            org = result.single();
            orgId = org.get("id").asString();
            employeesN = org.get("employeesN").asInt();
        } catch (NoSuchRecordException e) {
            // The query is guaranteed to return <= 1 results, so if.single() throws, it means there's none.
            // If no organization exists, create one and add Person to it
            orgId = createOrganization(tx);
            System.out.printf("No orgs available, created %s.%n", orgId);
        }

        // If org does not have too many employees, add this Person to it
        if (employeesN < employeeThreshold) {
            addPersonToOrganization(tx, name, orgId);
            // If the above throws, the transaction will roll back
            // -> not even Person is created!

        // Otherwise, create a new Organization and link Person to it
        } else {
            orgId = createOrganization(tx);
            System.out.printf("Latest org is full, created %s.%n", orgId);
            addPersonToOrganization(tx, name, orgId);
            // If any of the above throws, the transaction will roll back
            // -> not even Person is created!
        }

        return orgId;  // Organization ID to which the new Person ends up in
    }

    static String createOrganization(TransactionContext tx) {
        var result = tx.run("""
            CREATE (o:Organization {id: randomuuid(), createdDate: datetime()})
            RETURN o.id AS id
        """);
        var org = result.single();
        var orgId = org.get("id").asString();
        return orgId;
    }

    static void addPersonToOrganization(TransactionContext tx, String personName, String orgId) {
        tx.run("""
            MATCH (o:Organization {id: $orgId})
            MATCH (p:Person {name: $name})
            MERGE (p)-[:WORKS_FOR]->(o)
            """, Map.of("orgId", orgId, "name", personName)
        );
    }
}

关闭连接和会话

除非您使用try-with-resources语句创建它们,否则请对所有DriverSession实例调用.close()方法以释放它们仍持有的任何资源。

session.close();
driver.close();

API 文档

有关驱动程序功能的深入信息,请查看API 文档

词汇表

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 的 DBMS 确保数据库中的数据在发生故障时仍保持准确和一致。

最终一致性

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

因果一致性

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

NULL

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

事务

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

背压

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

事务函数

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

驱动程序

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