配置

驱动程序类名

Neo4j JDBC 驱动程序的类名为 org.neo4j.jdbc.Neo4jDriver。在使用现代 Java 工具时,通常不需要直接接触此类,但某些连接池和前端工具会要求提供该类名。该类属于公共 API。

我们还提供了 org.neo4j.jdbc.Neo4jDataSource,它实现了 javax.sql.DataSource 接口。

URL 和连接属性

Neo4j JDBC 驱动程序的规范 URL 格式为

列表 1. Neo4j JDBC 驱动程序的规范 JDBC URL 格式
jdbc:neo4j://<host>:<port>/<database>?param1=value1&param2=value2

数据库名称和所有查询参数均为可选,可以省略。所有配置参数既可以通过查询参数传递,也可以通过 java.util.Properties 对象传递。后者有时由工具为您定义。

关于身份验证,我们建议遵循 JDBC 规范,该规范不鼓励使用任何形式的 URL 身份验证。如果查询参数包含特殊字符(例如 …​?param1=space%20separated),则必须进行 百分比编码 (percent-encoded)

驱动程序支持以下 URI 方案,这些方案会调整安全配置:

  • neo4j - 无加密。

  • neo4j+s - 启用加密,且仅接受由已知证书颁发机构 (CA) 签名的服务器 SSL 证书。

  • neo4j+ssc - 启用加密,并接受自签名证书(必须将其添加到证书存储中)。

不支持 bolt URI 方案。

最后,当使用 2025.06 及更高版本且启用了 Http 查询 API 的 Neo4j 实例时,可以将 http 添加为子协议。

jdbc:neo4j:http//<host>:<port>/<database>?param1=value1&param2=value2
jdbc:neo4j:https//<host>:<port>/<database>?param1=value1&param2=value2

通常,Neo4j 在 7474 或 7473 端口提供 HTTP 服务。如果不指定端口,Neo4j JDBC 驱动程序默认将 http 和 https 分别指向 80 和 443 端口。

查询 API 在少数极端情况下的行为有所不同。特别是对于大数据集,驱动程序会将其完全物化,而不是像二进制 neo4j 协议那样流式传输单个记录。此外,如果查询产生 n 个结果,但在第 n+1 个结果时失败,通过 HTTP 运行时会立即报错。

某些功能(例如对 Vector 数据类型的支持和事务元数据)在 HTTP 上完全不支持。

驱动程序接受以下配置参数,既可以作为属性也可以作为 URL 查询参数:

表 1. 配置参数
名称 类型 含义 默认

timeout

整数

连接获取超时时间(毫秒)

1000

agent

字符串

用户代理 (User agent)

neo4j-jdbc

enableSQLTranslation

布尔值

启用 SQL 到 Cypher 自动转换的标志(需要在类路径中包含翻译器)

false

cacheSQLTranslations

布尔值

启用翻译缓存的标志。SQL 翻译并非“免费”:解析 SQL 和 Cypher® 渲染都需要一定时间。此外,我们可能需要查找元数据以映射单个属性。如果耗时过长,可以缓存翻译结果。

false

rewritePlaceholders

布尔值

允许在 Cypher 语句中使用 ? 作为占位符的标志(JDBC 要求)。这些占位符将自动重写为 $1, $2$n(从 1 开始),以便与 JDBC 从 1 开始的索引相匹配。

enableSQLTranslationfalse 时为 true,否则为 false

ssl

布尔值

可选标志,是 neo4j+s 的替代方案。例如,可用于以编程方式启用完整的 SSL 链。

null

sslMode

Enum<SSLMode>

用于对 SSL 配置进行精细控制的可选配置。允许的值为 disable, require, verify-full。详见 <ssl_mode, 理解 SSL 模式>>。

null

user(用户)

字符串

用于身份验证的用户名(主体)。出于安全考虑,不建议将其作为 URL 查询参数使用。

neo4j

password

字符串

用于身份验证的密码(凭据)。出于安全考虑,不建议将其作为 URL 查询参数使用。

password

authRealm

字符串

用于身份验证的领域 (Realm)。出于安全考虑,不建议将其作为 URL 查询参数使用。

null

authScheme

字符串

要使用的身份验证方案。出于安全考虑,不建议将其作为 URL 查询参数使用。目前支持的值为:

  • basic(默认)用于基本身份验证。

  • bearer 用于持有者身份验证 (SSO)。password 应设置为持有者令牌;userauthRealm 不起作用。

  • kerberos 用于 Kerberos 身份验证。需要将 password 设置为 Kerberos 票据;userauthRealm 不起作用。

  • none 如果服务器禁用了身份验证。user, password, authRealm 属性不起作用。

basic

useBookmarks

boolean

启用书签管理以获得完整的因果集群支持。默认情况下已启用,这是使用连接池的所有场景的推荐设置。如果禁用它,则仅对该特定连接禁用。从驱动程序实例获取的到相同或其他数据库的其他连接不受影响,且各个连接仍将管理其书签。

true

viewDefinitions

字符串

指向有效的 JSON 文件的文件路径、http 或 https URL,其中包含 Cypher 支持的视图的定义。

null

authn.supplier

字符串

身份验证提供程序的名称,可用于插件化各种 SSO 系统(如 Keycloak),并支持自动令牌刷新。有关更多信息,请参阅下文的 自定义身份验证

null

tryTcpFastOpen

boolean

将此值设置为 true 可使驱动程序尝试使用 TCP Fast Open。要使其工作,必须满足以下附加要求:

  • 驱动程序所在的系统支持并已启用 TCP Fast Open。

  • 驱动程序连接的 DBMS 也必须支持并启用 TCP Fast Open。

  • 类路径或模块路径中必须存在以下附加运行时依赖项之一(取决于您的操作系统):

    • netty-transport-native-io_uring (仅限 Netty 4.2+)

    • netty-transport-native-epoll

    • netty-transport-native-kqueue

null

只要添加了有效的 Netty 传输依赖项,它就会被发现并替代默认的 NIO 传输。这些依赖项与操作系统相关,有效选项为 netty-transport-native-epoll, netty-transport-native-kqueue 以及 Netty 4.2 或更高版本上的 netty-transport-native-io_uring

获取驱动程序或连接实例

本节仅适用于您在应用程序开发中使用 Neo4j JDBC 驱动程序的情况,而不是将其作为 DBeaver, DataGrip 或基于 UI 的 ETL 工具等前端工具的一部分使用。

获取连接的最简单方法是直接通过 java.sql.DriverManager

列表 2. 获取到 Neo4j 服务器的 JDBC 连接
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

class Configuration {

    void obtainConnection() throws SQLException {

        var url = "jdbc:neo4j://:7687";
        var username = "neo4j";
        var password = "verysecret";
        var connection = DriverManager.getConnection(url, username, password);
    }

}

虽然我们的连接实现是线程安全的,但每个连接只允许一个并发事务(根据 JDBC 规范)。对于多线程应用程序,请使用连接池。虽然有 HikariCP,但通常应用服务器和容器/框架会自带连接池。使用它们中的任何一个都是安全的,因为 Neo4j JDBC 驱动程序本身不进行内部连接池管理。

如果您需要访问 Neo4j 驱动程序本身的实例,可以使用以下方法:

列表 3. 获取 Neo4j JDBC 驱动程序实例
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

class Configuration {

    void obtainDriverAndConnection() throws SQLException {

        var url = "jdbc:neo4j://:7687";
        var driver = DriverManager.getDriver(url);

        var properties = new Properties();
        properties.put("username", "neo4j");
        properties.put("password", "verysecret");
        var connection = driver.connect(url, properties);
    }

}

使用 SSL 保护连接

Neo4j JDBC 驱动程序支持通用 Java 驱动程序的相同 SSL 选项,使用相同的 URL 协议,并使用 +s+ssc 作为所需安全级别的附加指示符。

相同的配置也可以通过 URL 查询参数,或在请求连接时传递给 DriverManager 或驱动程序实例的属性条目来实现。只要您不指定相互矛盾的值,结合使用这两种方法是可以的。

理解 SSL 模式

以下列表按安全性升序排列:

  • disable — (默认),“我不关心安全性,也不想支付加密带来的开销。”

  • require — “我希望数据被加密,并接受相应的开销。我相信网络能确保我始终连接到我想要的服务器。”(服务器必须支持加密,不进行主机名/CA 验证。这避免了配置证书的麻烦,仅在专用网络上安全;不应在公共网络上使用。)

  • verify-full — “我希望数据被加密,并接受相应的开销。我希望确保我连接的是我信任的服务器,并且是我指定的服务器。”

Neo4j JDBC 驱动程序不包含证书撤销检查。

最安全的选项也可以通过将 ssl=true 设置为查询参数或传递给 DriverManager 的属性条目来启用。此选项对应于 neo4j+s。另一方面,require 对应于 neo4j+ssc

附加的枚举类型使我们将来可能支持其他模式,例如让服务决定 SSL 设置,或者在不强制要求的情况下表达对 SSL 的偏好。

Neo4j 服务器可以同时提供普通的 Bolt 连接和加密的 SSL 连接,或仅提供其中之一。您可以使用 neo4j+s 进行连接,并不意味着您不能只使用 neo4j 进行连接,反之亦然。这取决于服务器的设置。Neo4j Aura(Neo4j 的托管云服务)仅支持加密连接,因此您必须使用 +s, ssl=truesslMode=verify-full

有效 URL

以下 URL 均有效:

neo4j+s://xyz.databases.neo4j.io

对 AuraDB 中的 xzy 实例使用完全验证

neo4j://xyz.databases.neo4j.io?ssl=true

相同,但使用简写 URL 参数

neo4j://xyz.databases.neo4j.io?sslMode=verify-full

相同,但使用显式模式

neo4j+s://xyz.databases.neo4j.io?ssl=true&sslMode=verify-full

没有更安全,但不会失败

neo4j+ssc://this.is.a.trustworthy.instance.for.sure.com

信任任何证书和主机名,但务必使用 SSL

neo4j://my-testing-instance.local

使用普通连接。

驱动程序仅会拒绝相互矛盾的配置,例如:

  • +sssl=false 连用,或 sslMode 设置为 disable

  • +sscssl=false 连用,或任何不等于 requiresslMode

基本上,您不能同时要求使用 SSL 又不使用它。驱动程序提供了多种机制,以便您可以将固定 URL 与动态查询参数结合使用,或使用动态 URL,或者以您喜欢的任何方式进行编程配置。

使用 .dotenv 文件

当您创建 Neo4j Aura 实例时,系统会要求您下载一个名为 Neo4j-9df57663-Created-2023-06-12.txt 类似的文本文件。这本质上是一个包含连接数据库所需信息的 .dotenv 文件。

这些文件可以通过 Neo4jDriver.fromEnv() 直接使用(请参阅 通过环境变量获取连接)。此方法有多个重载版本,允许您配置文件名和目录。此外,构建器允许您配置 Aura 文件中未包含的选项。

因果集群与书签

Neo4j JDBC 驱动程序默认使用书签以在所有 Neo4j 部署中提供因果一致性。书签是在驱动程序级别本身管理的,而不是在驱动程序实例生成的连接上管理的,因此由一个实例生成的所有连接都将参与同一个事务因果链。来自不同驱动程序实例的连接不会使用同一组书签,并且没有内置的机制来实现这一点。如果您需要这样做,可以直接访问 Neo4jDriver 类型以检索当前已知书签集合,并将它们传递给另一个驱动程序实例。

Neo4j 事务元数据

Neo4j 支持将元数据附加到事务中,请参阅 SHOW TRANSACTIONS。由于 JDBC 规范中没有显式的事务对象,Neo4j JDBC 驱动程序需要另一种机制使其可配置。

JDBC 驱动程序提供了扩展接口 Neo4jMetadataWriter。我们的驱动程序、连接实现以及所有语句变体都可以相应地进行解包。配置是累加的:为驱动程序实例配置的元数据将用于从该驱动程序生成的所有连接,连接可以添加进一步的元数据,语句也可以添加自己的元数据。在语句上添加的元数据优先级高于连接元数据,连接元数据的优先级高于驱动程序元数据。

列表 4. 配置事务元数据
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.neo4j.jdbc.Neo4jDriver;
import org.neo4j.jdbc.Neo4jMetadataWriter;

public final class TransactionMetadata {

    private static final Logger LOGGER = Logger.getLogger(TransactionMetadata.class.getPackageName());

    public static void main(String... args) throws SQLException {
        var url = "jdbc:neo4j://:7687";

        var driver = (Neo4jDriver) DriverManager.getDriver(url);
        driver.withMetadata(Map.of("md_from_driver", "v1", "will_be_overwritten", "irrelevant"));

        var properties = new Properties();
        properties.put("user", "neo4j");
        properties.put("password", "verysecret");

        try (
            var con = driver.connect(url, properties)
                .unwrap(Neo4jMetadataWriter.class)
                .withMetadata(Map.of("md_from_connection", "v2", "will_be_overwritten", "xxx"))
                .unwrap(Connection.class);
            var statement = con.createStatement()
                .unwrap(Neo4jMetadataWriter.class)
                .withMetadata(Map.of("md_from_stmt", "v3", "will_be_overwritten", "v4"))
                .unwrap(Statement.class)
        ) {
            try (var result = statement.executeQuery("SHOW TRANSACTIONS YIELD metaData")) {
                while (result.next()) {
                    var metaData = result.getObject("metaData", Map.class);
                    LOGGER.log(Level.INFO, "{0}", metaData);
                }
            }
        }
    }
}

输出将类似于

Juli 17, 2024 1:18:16 PM org.neo4j.jdbc.docs.TransactionMetadata main
INFORMATION: {md_from_driver=v1, md_from_connection=v2, md_from_stmt=v3, will_be_overwritten=v4}

日志记录

通过标准 Java 设置

要生成有用且详细的日志,必须配置 java.util.logging。这可以通过配置文件或以编程方式完成。以下部分包含一个示例配置,将全局日志级别设置为 INFO,将 Neo4j JDBC 驱动程序本身设置为 DEBUG,并将所有与网络相关的内容设置为 WARN

列表 5. 记录所有信息的 Neo4j JDBC 驱动程序日志配置
.level = INFO
handlers=java.util.logging.ConsoleHandler

# The handler must be configured for the lowest level it should catch
java.util.logging.ConsoleHandler.level=FINEST

# Enables all Logging for the JDBC Driver
org.neo4j.jdbc.level=ALL
# But reduces the noise on the network and the result set
org.neo4j.jdbc.result-set.level=WARNING
org.neo4j.jdbc.network.level=WARNING
# This is an example how to configure the outbound and inbound messages
org.neo4j.jdbc.network.OutboundMessageHandler.level=FINE
org.neo4j.jdbc.network.InboundMessageHandler.level=FINE

# This produces a date, followed by the level, 3 dashes, the message and the source of the log if available
java.util.logging.SimpleFormatter.format = %1$tFT%1$tk:%1$tM:%1$tS.%1$tL%1$tz %4$-15s --- %5$s [%2$s]%n

此类文件必须通过系统属性传递给正在运行的 Java 程序:

java -Djava.util.logging.config.file=docs/src/main/resources/logging-to-console.properties <your program>

将日志捕获到文件需要更改处理程序:

列表 6. 将所有 JDBC 日志写入文件的 Neo4j JDBC 驱动程序日志配置
.level = INFO
handlers=java.util.logging.ConsoleHandler

# Keep everything by default at INFO
java.util.logging.ConsoleHandler.level=INFO

# The handler must be configured for the lowest level it should catch
java.util.logging.FileHandler.level=FINEST
java.util.logging.FileHandler.pattern=jdbc.log
# XML Format by default
# java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter

# Change the handler for JDBC
org.neo4j.jdbc.handlers=java.util.logging.FileHandler
# Keep the rest as above
org.neo4j.jdbc.level=ALL
org.neo4j.jdbc.result-set.level=WARNING
org.neo4j.jdbc.network.level=WARNING
org.neo4j.jdbc.network.OutboundMessageHandler.level=FINE
org.neo4j.jdbc.network.InboundMessageHandler.level=FINE

java.util.logging.SimpleFormatter.format = %1$tFT%1$tk:%1$tM:%1$tS.%1$tL%1$tz %4$-15s --- %5$s [%2$s]%n
JDBC 驱动程序的诊断功能基于标准的 java.util.logging 框架。该框架本身的配置超出了本文档的范围。如需完整概述,您可以从 "Java 日志概述" 开始。Java 使用的格式化程序是 java.util.Formatter

在 Spring Boot 应用程序内部

Neo4j JDBC 驱动程序将集成到 Spring Boot 日志配置中,并从中获取所有相关的格式和其他设置。级别可以使用 Spring 理解的级别进行配置。以下配置等同于上面的配置:

列表 7. Spring Boot 配置文件中 Neo4j JDBC 驱动程序的日志记录配置
logging.level.org.neo4j.jdbc=trace
logging.level.org.neo4j.jdbc.result-set=warn
logging.level.org.neo4j.jdbc.network=warn
logging.level.org.neo4j.jdbc.network.OutboundMessageHandler=debug
logging.level.org.neo4j.jdbc.network.InboundMessageHandler=debug

在 Quarkus 应用程序内部

Neo4j JDBC 驱动程序将集成到 Quarkus 日志配置中,并从中获取所有相关的格式和其他设置。级别可以使用 Quarkus 理解的级别进行配置。以下配置等同于上面的配置:

列表 8. Quarkus 配置文件中 Neo4j JDBC 驱动程序的日志记录配置
quarkus.log.category."org.neo4j.jdbc".min-level=TRACE
quarkus.log.category."org.neo4j.jdbc".level=TRACE
quarkus.log.category."org.neo4j.jdbc.result-set".level=DEBUG
quarkus.log.category."org.neo4j.jdbc.network".level=WARNING
quarkus.log.category."org.neo4j.jdbc.network.OutboundMessageHandler".level=DEBUG
quarkus.log.category."org.neo4j.jdbc.network.InboundMessageHandler".level=DEBUG

SQL 翻译

SQL 翻译器使用记录器 org.neo4j.jdbc.translator;处理过的 SQL 及其原始源在 org.neo4j.jdbc.statement.SQL 上以 FINE 级别记录。

指标 (Metrics)

Neo4j JDBC 驱动程序将通过 Micrometer 发布指标。如果您将 Micrometer 放在类路径中,则无需进一步配置。例如,Spring Boot ActuatorMicrometer for Quarkus 会自动包含此功能。为了充分利用此功能,您还需要配置您的 监控系统

Neo4j JDBC 驱动程序发布以下指标:

org.neo4j.jdbc.connections

一个表示打开连接数的仪表 (gauge),带有连接 URL 标签

org.neo4j.jdbc.statements

一个表示打开语句数的仪表,带有打开语句的 URL 和 JDBC 语句类型(Statement, PreparedStatementCallableStatement)标签

org.neo4j.jdbc.queries

一个复合度量,包含成功和失败查询的计数,以及测量查询持续时间的计时器

org.neo4j.jdbc.cached-translations

一个表示缓存的 SQL 到 Cypher 翻译数量的仪表

追踪

Neo4j JDBC 驱动程序支持追踪,并将为 JDBC Statement 实例上的以下操作提供追踪跨度 (trace spans):

  • #execute

  • #executeQuery

  • #executeUpdate

以及它们的所有重载版本和所有继承类型。此外,在使用 JDBC ResultSet 时,它会在结果集从第一行之前首次移动到超出最后一行或被主动关闭时打开一个跨度。跨度将包含各种事件,例如查询何时由 Neo4j 数据库实际执行,或记录批次何时从数据库中提取。

该功能是可选的:当您想使用它时,必须引入额外的依赖项。这适用于所有分发版本,包括完整包,以确保可选功能不会增加包的大小。对于 Maven,请使用以下附加依赖项声明:

列表 9. 允许配置追踪的 Maven 依赖项
<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-jdbc-tracing-micrometer</artifactId>
    <version>6.9.1</version>
</dependency>

对于 Gradle,您需要声明以下运行时依赖项:

列表 10. 允许配置追踪的 Gradle 依赖项
dependencies {
    runtimeOnly 'org.neo4j:neo4j-jdbc-tracing-micrometer:6.9.1'
}

此依赖项引入了 Micrometer Tracing。Micrometer Tracing 允许您绑定到各种追踪器和导出器,例如 Zipkin 或 OpenTelemetry。

一旦拥有 Micrometer 追踪器,就可以按如下方式配置 Neo4jDataSource

列表 11. 生成追踪其使用的连接的 Neo4jDataSource
import javax.sql.DataSource;

import org.neo4j.jdbc.Neo4jDataSource;
import org.neo4j.jdbc.tracing.micrometer.Neo4jTracingBridge;

import io.micrometer.tracing.Tracer;

public class TracingExampleSetup {

    DataSource neo4jDataSource() {

        Tracer tracer = Tracer.NOOP; (1)
        var neo4jDataSource = new Neo4jDataSource();
        neo4jDataSource.setUrl("jdbc:neo4j://yourhost:7687");
        neo4jDataSource.setPassword("neo4j");
        neo4jDataSource.setUser("verysecret");
        neo4jDataSource.setTracer(Neo4jTracingBridge.to(tracer)); (2)
        return neo4jDataSource;
    }
}
1 在大多数情况下,您的应用程序框架将为您提供一个 Micrometer 追踪器实例。
2 通过此调用,您可以启用数据源以及从中生成的所有连接的追踪。

您还可以通过将单个连接解包为 Neo4jConnection 扩展来在它们上启用追踪:

列表 12. 为单个连接启用追踪
import java.sql.Connection;
import java.sql.DriverManager;

import org.neo4j.jdbc.Neo4jConnection;
import org.neo4j.jdbc.tracing.micrometer.Neo4jTracingBridge;

import io.micrometer.tracing.Tracer;

public class TracingExampleSetup {

    Connection connection() {

        Tracer tracer = Tracer.NOOP;
        Neo4jConnection connection = DriverManager.getConnection("jdbc:neo4j://yourhost:7687", "neo4j", "verysecret")
            .unwrap(Neo4jConnection.class); (1)
        connection.setTracer(Neo4jTracingBridge.to(tracer)); (2)
    }
}
1 将连接解包到我们的扩展中,以便配置追踪器
2 配置追踪器
如果您既不想使用数据源也不想使用单个连接,请使用 Neo4jDriver.registerTracer(Neo4jTracingBridge.to(tracer)) 向所有在 JDBC DriverManager 中注册的 Neo4j 驱动程序注册追踪器。

在 Spring Boot 中使用追踪

Neo4j JDBC 驱动程序提供的追踪实现利用了 Micrometer Tracing,这在 Spring Boot 生态系统中得到了广泛认可,在 Quarkus、Micronaut 等中也是如此。我们提供了一些示例,但请参阅您框架的文档了解如何配置追踪。

如果您乐意为通过 DriverManager 获取的所有连接配置追踪,请按如下方式配置:

列表 13. 为所有在 DriverManager 中注册的驱动程序配置追踪
import io.micrometer.tracing.Tracer;
import org.neo4j.jdbc.tracing.micrometer.Neo4jTracingBridge;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TracingConfiguration {

    @Bean
    InitializingBean neo4jTracingInitializer(Tracer tracer) {
        return () -> Neo4jDriver.registerTracer(Neo4jTracingBridge.to(tracer));
    }
}

否则,请使用如下所示的自定义数据源。

配置追踪数据源

通常,Spring Boot 会根据您的驱动程序配置 HikariDataSource。Hikari 提供连接池,您不应放弃它。无需为所有配置值创建自己的数据源属性,只需按如下方式注入即可:

列表 14. 使用 Spring Boot 配置追踪和池化数据源
import javax.sql.DataSource;

import org.neo4j.jdbc.Neo4jDataSource;
import org.neo4j.jdbc.tracing.micrometer.Neo4jTracingBridge;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.micrometer.tracing.Tracer;

@Configuration
class TracingTestConfiguration {

    @Bean
    DataSource neo4jDataSource(Tracer tracer, DataSourceProperties dataSourceProperties) {

        var neo4jDataSource = new Neo4jDataSource();
        neo4jDataSource.setUrl(dataSourceProperties.getUrl());
        neo4jDataSource.setPassword(dataSourceProperties.getPassword());
        neo4jDataSource.setUser(dataSourceProperties.getUsername());
        neo4jDataSource.setTracer(Neo4jTracingBridge.to(tracer));

        var cfg = new HikariConfig();
        cfg.setDataSource(neo4jDataSource);
        return new HikariDataSource(cfg);
    }
}

org.springframework.boot:spring-boot-starter-actuator 也添加到类路径中,它将为您配置 Micrometer。

您的数据源设置保持不变:对于以下示例要工作,此处无需进行任何更改,因为我们的追踪 API 将通过 Micrometer 自动选择正确的追踪器!

导出到 Zipkin

要将追踪导出到 Zipkin,请添加 io.micrometer:micrometer-tracing-bridge-braveio.zipkin.reporter2:zipkin-reporter-brave,并在 Spring 应用程序中将 management.zipkin.tracing.export.enabled 设置为 true

您应该会看到如下所示的跨度:

zipkintracing

通过 OpenTelemetry 导出

OpenTelemetry 不仅允许导出追踪,还允许导出指标和日志。

以下是通过 OpenTelemetry 使用 Dash0 的示例。导出器对于其他服务应该同样有效。OpenTelemetry 已将其 SDK 分割为多个模块,因此您首先需要将它们的 BOM 导入构建文件。以下是 Maven 的示例:

列表 15. Maven 构建文件中包含 OpenTelemetry BOM 的片段
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-bom</artifactId>
    <version>1.47.0</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

首先,您需要通过以下方式引入从 Micrometer 到 OpenTelemetry 的桥接:io.micrometer:micrometer-tracing-bridge-otel,以便 Micrometer 推送到该 SDK。接下来,引入以下依赖项,以便 Spring Boot 可以配置导出器:

  • io.opentelemetry:opentelemetry-sdk

  • io.opentelemetry:opentelemetry-exporter-common

  • io.opentelemetry:opentelemetry-exporter-otlp

  • io.opentelemetry:opentelemetry-exporter-sender-jdk

对于 Dash0,应用程序配置将类似于:

列表 16. 在 Spring Boot 中向 Dash0 使用 OpenTelemetry 导出器
management.otlp.tracing.export.enabled=true
management.otlp.tracing.endpoint=https://ingress.eu-west-1.aws.dash0.com/v1/traces
management.otlp.tracing.headers.Authorization=Bearer auth_XX_your_token_XX

management.otlp.metrics.export.step=30s
management.otlp.metrics.export.enabled=true
management.otlp.metrics.export.url=https://ingress.eu-west-1.aws.dash0.com/v1/metrics
management.otlp.metrics.export.headers.Authorization=Bearer auth_XX_your_token_XX

我们的电影应用程序的追踪记录如下:

dash0tracing

自定义身份验证

Neo4j JDBC 驱动程序除了 JDBC 标准身份验证方式(例如使用 java.sql.DriverManager#getConnection(java.lang.String, java.lang.String, java.lang.String)java.sql.Driver#connect 进行服务器身份验证)之外,还提供了使用自定义身份验证提供程序的功能。

身份验证提供程序在 Java 中是指 Supplier<org.neo4j.jdbc.authn.api.Authentication>Authentication 实例是提供给 Neo4j 服务器的任意身份验证的抽象表示,可以包含主体及其凭据(用户名和密码)、令牌或其他确认身份的方式。

该接口本身提供以下工厂方法:

Authentication#usernameAndPassword

创建简单的基于用户名和密码的身份验证,这正是默认方法在内部已使用的内容。

Authentication#none

禁用身份验证

Authentication#bearer

创建基于持有者令牌的身份验证

后者允许设置令牌的过期日期。如果令牌过期,则在过期日期之后,会要求提供程序获取新令牌。这允许配置 Neo4j JDBC 驱动程序,使其可以从 SSO 提供程序(如 KeycloakOkta)获取令牌,并使用这些令牌针对 Neo4j 进行身份验证,并在它们过期时自动刷新。

此外,如果上述工厂方法不够用,还可以实现 CustomAuthenticationExpiringAuthentication 两个接口。

有几种方法可以传递身份验证提供程序:

  • 创建一个包含自定义身份验证提供程序和实现 AuthenticationSupplierFactory 的小型工厂的库:它将自动加载到 JDBC 驱动程序中,并在 JDBC 属性 authn.supplier 设置为工厂名称时使用。

  • 在驱动程序本身上,当解包为 Neo4jDriver 时,可以使用 driver#connect(String, Properties, Supplier)。给定的提供程序将优先于 properties 参数中的任何身份验证信息。

  • 使用 driver#setAuthenticationSupplier(Supplier) 在驱动程序的特定实例上设置提供程序实例。

  • 使用 Neo4jDriver#registerAuthenticationSupplier 注册全局提供程序实例,该实例将用于所有由 DriverManager 生成的连接。

  • Neo4jDriver 上作为静态方法存在的便捷构建器方法(允许从环境配置驱动程序,请参阅 通过环境变量获取连接)也允许指定提供程序。

优先级如下:

  1. 如果传递给 connect 的有提供程序,它将优先于驱动程序本地配置的提供程序、全局提供程序以及任何属性。

  2. 如果在 JDBC 属性中有名为 authn.supplier 的属性,并且提供了具有该名称的身份验证提供程序工厂,则将从该工厂获取并使用提供程序。

  3. 每个驱动程序配置的提供程序或全局提供程序优先于任何属性。

  4. 当没有显式或隐式身份验证提供程序时,将按照 JDBC 标准使用属性。

驱动程序仅在尚未检索到令牌时或第一个令牌过期时调用提供程序。任何身份验证提供程序实现都可以安全地假定第二次调用令牌意味着第一个令牌已过期,因此提供程序不需要管理状态。

底层的 Bolt 协议和 Neo4j 服务器可能无法与寿命极短的身份验证令牌(即寿命在 5 秒以下)可靠配合。此限制将来可能会取消,但截至 6.6.0 版本,您可能需要避免使用寿命极短的令牌。

Keycloak 示例

以下类显示了一个基于 Keycloak 的 AuthzClient 的示例。提供程序将从 Keycloak 实例获取令牌,并将它们的过期日期与 JWT 令牌一起存储。最初请求令牌时将始终返回一个全新的令牌,后续每次调用都将使用客户端刷新该令牌。

无需将以下代码复制到您自己的应用程序中。我们确实在可选的 org.neo4j:neo4j-jdbc-authn-kc 模块中提供了此身份验证提供程序。该提供程序是我们 semver API 合约的一部分,可以直接使用,也可以通过服务加载器机制使用,这在以下文档中也有演示。
列表 17. KCAuthenticationSupplier,作为在必要时刷新令牌的身份验证提供程序示例
import java.io.IOException;
import java.io.UncheckedIOException;
import java.time.Instant;
import java.util.Base64;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

import com.fasterxml.jackson.jr.ob.JSON;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.util.Http;
import org.keycloak.representations.AccessTokenResponse;
import org.neo4j.jdbc.authn.spi.Authentication;

public final class KCAuthenticationSupplier implements Supplier<Authentication> {

    public static Supplier<Authentication> of(String user, String password, Configuration configuration) {
        return new KCAuthenticationSupplier(user, password, configuration);
    }

    private final String username;

    private final String password;

    private final Configuration cfg;

    private final AuthzClient authzClient;

    private final Http http;

    private final String url;

    private final AtomicReference<TokensAndExpirationTime> currentToken = new AtomicReference<>();

    KCAuthenticationSupplier(String user, String password, Configuration cfg) {
        this.username = user;
        this.password = password;
        this.cfg = cfg;
        this.authzClient = AuthzClient.create(cfg);
        this.url = "%s/realms/%s/protocol/openid-connect/token".formatted(cfg.getAuthServerUrl(), cfg.getRealm());
        this.http = new Http(cfg, cfg.getClientCredentialsProvider());
    }

    @Override
    public Authentication get() {
        return this.currentToken.updateAndGet(previous -> {
            if (previous == null) {
                return get0();
            }
            return refresh0(previous.refreshToken());
        }).toAuthentication();
    }

    TokensAndExpirationTime get0() {
        try {
            return TokensAndExpirationTime.of(this.authzClient.obtainAccessToken(this.username, this.password));
        }
        catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    TokensAndExpirationTime refresh0(String refreshToken) {
        try {
            return TokensAndExpirationTime.of(this.http.<AccessTokenResponse>post(this.url)
                .authentication()
                .client()
                .form()
                .param("grant_type", "refresh_token")
                .param("refresh_token", refreshToken)
                .param("client_id", this.cfg.getResource())
                .param("client_secret", (String) this.cfg.getCredentials().get("secret"))
                .response()
                .json(AccessTokenResponse.class)
                .execute());
        }
        catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    record TokensAndExpirationTime(String accessToken, Instant expiresAt, String refreshToken) {

        static final Base64.Decoder DECODER = Base64.getDecoder();

        static TokensAndExpirationTime of(AccessTokenResponse accessTokenResponse) throws IOException {
            var chunks = accessTokenResponse.getToken().split("\\.");
            var payload = JSON.std.mapFrom(DECODER.decode(chunks[1]));
            var exp = Instant.ofEpochSecond(((Number) payload.get("exp")).longValue());
            return new TokensAndExpirationTime(accessTokenResponse.getToken(), exp,
                    accessTokenResponse.getRefreshToken());
        }

        Authentication toAuthentication() {
            return Authentication.bearer(this.accessToken, this.expiresAt);
        }

    }

}

同样,要使用上述提供程序,请将以下依赖项添加到您的类路径或模块路径中,不要复制这些代码,它们仅作为您如何编写自己的身份验证提供程序的示例显示在这里。

列表 18. 核心 Neo4j JDBC 驱动程序制品的 Maven 依赖项
<dependency>
    <groupId>org.neo4j</groupId>
    <artifactId>neo4j-jdbc-authn-kc</artifactId>
    <version>6.9.1</version>
</dependency>

现在使用提供程序的最简单方法是通过内置加载器,这样您可以完全遵循 JDBC API:

列表 19. 通过 authn.supplier 配置选项使用自定义身份验证提供程序
@Test
void authenticationSupplierFactoriesShouldWork() throws SQLException {

    var properties = new Properties();
    properties.put("enableSQLTranslation", "true");

    properties.put("user", "william.foster");
    properties.put("password", "d-fens");

    properties.put("authn.supplier", "kc"); (1)
    properties.put("authn.kc.authServerUrl", "https://:%d".formatted(KEYCLOAK.getHttpPort())); (2)
    properties.put("authn.kc.realm", "neo4j-sso-test");
    properties.put("authn.kc.clientId", "neo4j-jdbc-driver");
    properties.put("authn.kc.clientSecret", "QcWXnTg8qJpVMnIvm8Ev8gp1PqJitZu4");

    try (var connection = DriverManager.getConnection(neo4jUri, properties); (3)
            var stmt = connection.createStatement();
            var rs = stmt.executeQuery("SELECT 1")) {
        assertThat(rs.next()).isTrue();
        assertThat(rs.getInt(1)).isOne();
    }
}
1 指示您要使用 Keycloak 身份验证提供程序(在我们的分发中命名为 kc
2 此属性及后续属性将传递给身份验证系统,以便它可以使用 Keycloak 创建身份验证
3 这是一个纯 JDBC 规范调用

如果您不想使用身份验证提供程序工厂的自动加载器,或者您编写了一个完全自定义的身份验证提供程序且不想提供 services 和相应的模块描述,还有其他几种使用身份验证提供程序的方法。

最符合 JDBC 的方式是为其注册全局驱动程序,如下所示:

列表 20. 全局注册身份验证提供程序实例
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.function.Supplier;

import org.keycloak.authorization.client.Configuration;
import org.neo4j.jdbc.authn.kc.KCAuthenticationSupplier;
import org.neo4j.jdbc.authn.spi.Authentication;
import org.neo4j.jdbc.Neo4jDriver;

class Example {

	public static void main(String...a) throws Exception {
		Supplier<Authentication> authenticationSupplier = KCAuthenticationSupplier.of("user", "pass", new Configuration());
        Neo4jDriver.registerAuthenticationSupplier(authenticationSupplier);
        Connection connection = DriverManager.getConnection("neo4j+s://xyz.databases.neo4j.io");
    }
}

这里的优点是它确保了所有通过 DriverManager 以符合 JDBC 标准的方式使用 Neo4j JDBC 驱动程序的应用程序都将自动使用该身份验证提供程序。因此,上述设置很可能是与 Spring Boot 等框架集成的最简单方法,您可以通过 org.springframework.beans.factory.InitializingBean 配置全局提供程序。或者使用驱动程序构建器或手动创建驱动程序实例。

列表 21. 创建 Neo4jDriver 的显式实例,以便接收身份验证提供程序的 connect 重载可见
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
import java.util.function.Supplier;

import org.keycloak.authorization.client.Configuration;
import org.neo4j.jdbc.authn.kc.KCAuthenticationSupplier;
import org.neo4j.jdbc.authn.spi.Authentication;
import org.neo4j.jdbc.Neo4jDriver;

class Example {

    public static void main(String...a) throws Exception {
        Supplier<Authentication> authenticationSupplier = KCAuthenticationSupplier.of("user", "pass", new Configuration());
        Neo4jDriver.registerAuthenticationSupplier(authenticationSupplier);
        Neo4jDriver driver = new Neo4jDriver();
        Connection connection = driver.connect("neo4j+s://xyz.databases.neo4j.io", new Properties(), authenticationSupplier);
    }
}

对于您可以完全控制创建连接的任何应用程序,此替代方案是可行的。

如果您需要针对 Keycloak 进行开发,Neo4j JDBC 驱动程序提供了您所需的一切,您可以直接开始使用。只需包含 Maven 模块,并应用通过 JDBC 属性配置提供程序的推荐方式。仅在有其他身份验证需求时才编写自定义身份验证提供程序。