DuckDB Java (JDBC) 客户端的最新版本是 1.3.2。
安装
DuckDB Java JDBC API 可以从 Maven Central 安装。详情请参阅安装页面。
基本 API 用法
DuckDB 的 JDBC API 实现了标准 Java Database Connectivity (JDBC) API 4.1 版的主要部分。本文档不涉及 JDBC 的详细描述,更多信息请参阅官方文档。下面我们将重点介绍 DuckDB 特有的部分。
有关我们对 JDBC 规范的扩展的更多信息,请参阅外部托管的API 参考,或下面的Arrow 方法。
启动与关闭
在 JDBC 中,数据库连接通过标准的 java.sql.DriverManager
类创建。驱动程序应该在 DriverManager
中自动注册,如果由于某种原因无法自动注册,您可以使用以下语句强制注册:
Class.forName("org.duckdb.DuckDBDriver");
要创建 DuckDB 连接,请使用 jdbc:duckdb:
JDBC URL 前缀调用 DriverManager
,如下所示:
import java.sql.Connection;
import java.sql.DriverManager;
Connection conn = DriverManager.getConnection("jdbc:duckdb:");
要使用 DuckDB 特有的功能,例如追加器,请将对象转换为 DuckDBConnection
:
import java.sql.DriverManager;
import org.duckdb.DuckDBConnection;
DuckDBConnection conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:");
单独使用 jdbc:duckdb:
URL 时,会创建一个内存数据库。请注意,对于内存数据库,数据不会持久化到磁盘(即,当您退出 Java 程序时,所有数据都会丢失)。如果您想访问或创建一个持久化数据库,请在路径后附加其文件名。例如,如果您的数据库存储在 /tmp/my_database
中,请使用 JDBC URL jdbc:duckdb:/tmp/my_database
连接到它。
可以以只读模式打开 DuckDB 数据库文件。例如,当多个 Java 进程需要同时读取同一个数据库文件时,这会很有用。要以只读模式打开现有数据库文件,请如下设置连接属性 duckdb.read_only
:
Properties readOnlyProperty = new Properties();
readOnlyProperty.setProperty("duckdb.read_only", "true");
Connection conn = DriverManager.getConnection("jdbc:duckdb:/tmp/my_database", readOnlyProperty);
可以使用 DriverManager
创建其他连接。更高效的机制是调用 DuckDBConnection#duplicate()
方法:
Connection conn2 = ((DuckDBConnection) conn).duplicate();
允许多个连接,但不支持混合读写和只读连接。
配置连接
可以提供配置选项来更改数据库系统的各种设置。请注意,其中许多设置也可以稍后使用PRAGMA
语句进行更改。
Properties connectionProperties = new Properties();
connectionProperties.setProperty("temp_directory", "/path/to/temp/dir/");
Connection conn = DriverManager.getConnection("jdbc:duckdb:/tmp/my_database", connectionProperties);
查询
DuckDB 支持标准的 JDBC 方法来发送查询和检索结果集。首先需要从 Connection
创建一个 Statement
对象,然后可以使用该对象通过 execute
和 executeQuery
发送查询。execute()
用于不期望返回结果的查询,例如 CREATE TABLE
或 UPDATE
等;而 executeQuery()
用于产生结果的查询(例如 SELECT
)。下面是两个示例。另请参阅 JDBC Statement
和 ResultSet
文档。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
Connection conn = DriverManager.getConnection("jdbc:duckdb:");
// create a table
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE items (item VARCHAR, value DECIMAL(10, 2), count INTEGER)");
// insert two items into the table
stmt.execute("INSERT INTO items VALUES ('jeans', 20.0, 1), ('hammer', 42.2, 2)");
try (ResultSet rs = stmt.executeQuery("SELECT * FROM items")) {
while (rs.next()) {
System.out.println(rs.getString(1));
System.out.println(rs.getInt(3));
}
}
stmt.close();
jeans
1
hammer
2
DuckDB 也支持 JDBC API 中定义的预处理语句:
import java.sql.PreparedStatement;
try (PreparedStatement stmt = conn.prepareStatement("INSERT INTO items VALUES (?, ?, ?);")) {
stmt.setString(1, "chainsaw");
stmt.setDouble(2, 500.0);
stmt.setInt(3, 42);
stmt.execute();
// more calls to execute() possible
}
警告 请勿 使用预处理语句向 DuckDB 插入大量数据。有关更好的选项,请参阅数据导入文档。
Arrow 方法
有关类型签名,请参阅API 参考。
Arrow 导出
以下演示了如何导出 Arrow 流并使用 Java Arrow 绑定对其进行消费:
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.ipc.ArrowReader;
import org.duckdb.DuckDBResultSet;
try (var conn = DriverManager.getConnection("jdbc:duckdb:");
var stmt = conn.prepareStatement("SELECT * FROM generate_series(2000)");
var resultset = (DuckDBResultSet) stmt.executeQuery();
var allocator = new RootAllocator()) {
try (var reader = (ArrowReader) resultset.arrowExportStream(allocator, 256)) {
while (reader.loadNextBatch()) {
System.out.println(reader.getVectorSchemaRoot().getVector("generate_series"));
}
}
stmt.close();
}
Arrow 导入
以下演示了如何从 Java Arrow 绑定消费 Arrow 流。
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.ipc.ArrowReader;
import org.duckdb.DuckDBConnection;
// Arrow binding
try (var allocator = new RootAllocator();
ArrowStreamReader reader = null; // should not be null of course
var arrow_array_stream = ArrowArrayStream.allocateNew(allocator)) {
Data.exportArrayStream(allocator, reader, arrow_array_stream);
// DuckDB setup
try (var conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:")) {
conn.registerArrowStream("asdf", arrow_array_stream);
// run a query
try (var stmt = conn.createStatement();
var rs = (DuckDBResultSet) stmt.executeQuery("SELECT count(*) FROM asdf")) {
while (rs.next()) {
System.out.println(rs.getInt(1));
}
}
}
}
流式结果
JDBC 驱动程序中的结果流式传输是可选的——通过在运行查询之前将 jdbc_stream_results
配置设置为 true
。最简单的方法是在 Properties
对象中传递它。
Properties props = new Properties();
props.setProperty(DuckDBDriver.JDBC_STREAM_RESULTS, String.valueOf(true));
Connection conn = DriverManager.getConnection("jdbc:duckdb:", props);
追加器
DuckDB JDBC 驱动程序通过 org.duckdb.DuckDBAppender
类提供了追加器。该类的构造函数需要其所应用于的模式名和表名。当调用 close()
方法时,追加器会被刷新。
示例
import java.sql.DriverManager;
import java.sql.Statement;
import org.duckdb.DuckDBConnection;
DuckDBConnection conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:");
try (var stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE tbl (x BIGINT, y FLOAT, s VARCHAR)"
);
// using try-with-resources to automatically close the appender at the end of the scope
try (var appender = conn.createAppender(DuckDBConnection.DEFAULT_SCHEMA, "tbl")) {
appender.beginRow();
appender.append(10);
appender.append(3.2);
appender.append("hello");
appender.endRow();
appender.beginRow();
appender.append(20);
appender.append(-8.1);
appender.append("world");
appender.endRow();
}
批处理写入器
DuckDB JDBC 驱动程序提供批处理写入功能。批处理写入器支持预处理语句,以减轻查询解析的开销。
批量插入的首选方法是使用追加器,因为它具有更高的性能。但是,当无法使用追加器时,批处理写入器可作为替代方案。
使用预处理语句的批处理写入器
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import org.duckdb.DuckDBConnection;
DuckDBConnection conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:");
PreparedStatement stmt = conn.prepareStatement("INSERT INTO test (x, y, z) VALUES (?, ?, ?);");
stmt.setObject(1, 1);
stmt.setObject(2, 2);
stmt.setObject(3, 3);
stmt.addBatch();
stmt.setObject(1, 4);
stmt.setObject(2, 5);
stmt.setObject(3, 6);
stmt.addBatch();
stmt.executeBatch();
stmt.close();
使用普通语句的批处理写入器
批处理写入器也支持普通的 SQL 语句:
import java.sql.DriverManager;
import java.sql.Statement;
import org.duckdb.DuckDBConnection;
DuckDBConnection conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:");
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE test (x INTEGER, y INTEGER, z INTEGER)");
stmt.addBatch("INSERT INTO test (x, y, z) VALUES (1, 2, 3);");
stmt.addBatch("INSERT INTO test (x, y, z) VALUES (4, 5, 6);");
stmt.executeBatch();
stmt.close();
故障排除
未找到驱动程序类
如果 Java 应用程序无法找到 DuckDB,可能会抛出以下错误:
Exception in thread "main" java.sql.SQLException: No suitable driver found for jdbc:duckdb:
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:706)
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:252)
...
当尝试手动加载类时,可能会导致此错误:
Exception in thread "main" java.lang.ClassNotFoundException: org.duckdb.DuckDBDriver
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:375)
...
这些错误源于未检测到 DuckDB Maven/Gradle 依赖。为确保其被检测到,请在您的 IDE 中强制刷新 Maven 配置。