Zum Inhalt springen

Java-Client (JDBC)

Installation Um den DuckDB-Java-Client (JDBC) zu verwenden, besuchen Sie die Java-Installationsseite.

Die aktuellste stabile Version des DuckDB-Java-Clients (JDBC) ist 1.5.5.

Installation

Die DuckDB-Java-JDBC-API kann von Maven Central installiert werden. Details finden Sie auf der Installationsseite.

Grundlegende API-Nutzung

Die JDBC-API von DuckDB implementiert die wesentlichen Teile der standardmäßigen Java-Database-Connectivity-API (JDBC), Version 4.1. JDBC selbst zu beschreiben, sprengt den Rahmen dieser Seite; Details finden Sie in der offiziellen Dokumentation. Im Folgenden konzentrieren wir uns auf die DuckDB-spezifischen Teile.

Weitere Informationen zu unseren Erweiterungen der JDBC-Spezifikation finden Sie in der extern gehosteten API-Referenz oder unten unter Arrow-Methoden.

Start und Beenden

In JDBC werden Datenbankverbindungen über die Standardklasse java.sql.DriverManager erstellt. Der Treiber sollte sich automatisch im DriverManager registrieren; falls das aus irgendeinem Grund nicht funktioniert, können Sie die Registrierung mit der folgenden Anweisung erzwingen:

Class.forName("org.duckdb.DuckDBDriver");

Um eine DuckDB-Verbindung zu erstellen, rufen Sie DriverManager mit dem JDBC-URL-Präfix jdbc:duckdb: auf, etwa so:

import java.sql.Connection;
import java.sql.DriverManager;
Connection conn = DriverManager.getConnection("jdbc:duckdb:");

Um DuckDB-spezifische Funktionen wie den Appender zu nutzen, casten Sie das Objekt zu einer DuckDBConnection:

import java.sql.DriverManager;
import org.duckdb.DuckDBConnection;
DuckDBConnection conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:");

Wenn Sie nur die URL jdbc:duckdb: verwenden, wird eine In-Memory-Datenbank erstellt. Beachten Sie, dass bei einer In-Memory-Datenbank keine Daten auf die Festplatte geschrieben werden (d. h. alle Daten gehen verloren, wenn Sie das Java-Programm beenden). Wenn Sie auf eine persistente Datenbank zugreifen oder eine solche erstellen möchten, hängen Sie den Dateinamen an den Pfad an. Wenn Ihre Datenbank beispielsweise in /tmp/my_database gespeichert ist, verwenden Sie die JDBC-URL jdbc:duckdb:/tmp/my_database, um eine Verbindung dazu herzustellen.

Es ist möglich, eine DuckDB-Datenbankdatei im Nur-Lese-Modus zu öffnen. Das ist beispielsweise nützlich, wenn mehrere Java-Prozesse dieselbe Datenbankdatei gleichzeitig lesen sollen. Um eine vorhandene Datenbankdatei im Nur-Lese-Modus zu öffnen, setzen Sie die Verbindungseigenschaft duckdb.read_only wie folgt:

Properties readOnlyProperty = new Properties();
readOnlyProperty.setProperty("duckdb.read_only", "true");
Connection conn = DriverManager.getConnection("jdbc:duckdb:/tmp/my_database", readOnlyProperty);

Weitere Verbindungen können über den DriverManager erstellt werden. Ein effizienterer Mechanismus ist der Aufruf der Methode DuckDBConnection#duplicate():

Connection conn2 = ((DuckDBConnection) conn).duplicate();

Mehrere Verbindungen sind erlaubt, das Mischen von Lese-/Schreib- und Nur-Lese-Verbindungen wird jedoch nicht unterstützt.

Verbindungen konfigurieren

Konfigurationsoptionen können angegeben werden, um verschiedene Einstellungen des Datenbanksystems zu ändern. Beachten Sie, dass viele dieser Einstellungen später auch mit PRAGMA-Anweisungen geändert werden können.

Properties connectionProperties = new Properties();
connectionProperties.setProperty("temp_directory", "/path/to/temp/dir/");
Connection conn = DriverManager.getConnection("jdbc:duckdb:/tmp/my_database", connectionProperties);

Abfragen

DuckDB unterstützt die standardmäßigen JDBC-Methoden zum Senden von Abfragen und zum Abrufen von Ergebnismengen. Zuerst muss aus der Connection ein Statement-Objekt erstellt werden; dieses Objekt kann dann verwendet werden, um Abfragen mit execute und executeQuery zu senden. execute() ist für Abfragen gedacht, bei denen keine Ergebnisse erwartet werden, etwa CREATE TABLE oder UPDATE usw., und executeQuery() ist für Abfragen gedacht, die Ergebnisse liefern (z. B. SELECT). Unten zwei Beispiele. Siehe auch die JDBC-Dokumentationen zu Statement und 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 unterstützt außerdem vorbereitete Anweisungen gemäß der 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
}

Warning Verwenden Sie keine vorbereiteten Anweisungen, um große Datenmengen in DuckDB einzufügen. Bessere Optionen finden Sie in der Dokumentation zum Datenimport.

Arrow-Methoden

Typsignaturen finden Sie in der API-Referenz

Arrow-Export

Das Folgende zeigt den Export eines Arrow-Streams und dessen Verbrauch über die Java-Arrow-Bindings.

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-Import

Das Folgende zeigt den Verbrauch eines Arrow-Streams aus den Java-Arrow-Bindings.

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));
}
}
}
}

Streaming-Ergebnisse

Ergebnis-Streaming ist im JDBC-Treiber opt-in – indem Sie die Konfiguration jdbc_stream_results vor dem Ausführen einer Abfrage auf true setzen. Am einfachsten übergeben Sie das im Properties-Objekt.

Properties props = new Properties();
props.setProperty(DuckDBDriver.JDBC_STREAM_RESULTS, String.valueOf(true));
Connection conn = DriverManager.getConnection("jdbc:duckdb:", props);

Appender

Der Appender ist im DuckDB-JDBC-Treiber über die Klasse org.duckdb.DuckDBAppender verfügbar. Der Konstruktor der Klasse benötigt den Schemanamen und den Tabellennamen, auf den er angewendet wird. Der Appender wird geleert, wenn die Methode close() aufgerufen wird.

Beispiel:

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

Batch Writer

Der DuckDB-JDBC-Treiber bietet Batch-Schreibfunktionalität. Der Batch Writer unterstützt vorbereitete Anweisungen, um den Aufwand des Query-Parsings zu verringern.

Die bevorzugte Methode für Masseneinfügungen ist der Appender aufgrund seiner höheren Leistung. Wenn der Appender jedoch nicht verwendet werden kann, steht der Batch Writer als Alternative zur Verfügung.

Batch Writer mit vorbereiteten Anweisungen

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

Batch Writer mit einfachen Anweisungen

Der Batch Writer unterstützt auch einfache SQL-Anweisungen:

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

Fehlerbehebung

Treiberklasse nicht gefunden

Wenn die Java-Anwendung den DuckDB-Treiber nicht findet, kann sie den folgenden Fehler auslösen:

Terminal window
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)
...

Und beim manuellen Laden der Klasse kann dieser Fehler entstehen:

Terminal window
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)
...

Diese Fehler entstehen, weil die DuckDB-Maven-/Gradle-Abhängigkeit nicht erkannt wird. Damit sie erkannt wird, erzwingen Sie in Ihrer IDE eine Aktualisierung der Maven-Konfiguration.