Verwenden des JDBC-Treibers

Unter diesem Thema werden Informationen zur Verwendung des JDBC-Treibers bereitgestellt.

Unter diesem Thema:

Snowflake-JDBC-API-Erweiterungen

Der Snowflake-JDBC-Treiber unterstützt zusätzliche Methoden, die über JDBC-die Standardspezifikation hinausgehen. In diesem Abschnitt wird beschrieben, wie Sie beim Entpacken den Zugriff auf die Snowflake-spezifischen Methoden bereitstellen. Anschließend werden drei Situationen beschrieben, in denen ein Entpacken möglicherweise erforderlich ist:

Entpacken von Snowflake-spezifischen Klassen

Der Snowflake-JDBC-Treiber unterstützt Snowflake-spezifische Methoden. Folgende Methoden sind Snowflake-spezifische, in Java codierte Schnittstellen wie SnowflakeConnection, SnowflakeStatement und SnowflakeResultSet. Beispielsweise enthält die SnowflakeStatement-Schnittstelle eine getQueryID()-Methode, die in der JDBC-Statement-Schnittstelle nicht definiert ist.

Wenn der Snowflake-JDBC-Treiber aufgefordert wird, ein JDBC-Objekt zu erstellen (z. B. Erstellen eines JDBC-Statement-Objekts durch Aufrufen der createStatement()-Methode eines Connection-Objekts), erstellt der Snowflake-JDBC-Treiber tatsächlich Snowflake-spezifische Objekte, die nicht nur die Methoden des JDBC-Standards, sondern auch die zusätzlichen Methoden der Snowflake-Schnittstellen implementieren.

Um auf diese Snowflake-Methoden zuzugreifen, „entpacken“ Sie das Objekt (z. B. ein Statement-Objekt), sodass das Snowflake-Objekt und seine Methoden verfügbar sind. Sie können dann die zusätzlichen Methoden aufrufen.

Der folgende Code zeigt, wie Sie ein JDBC-Statement-Objekt entpacken, um die Methoden der SnowflakeStatement-Schnittstelle verfügbar zu machen, und dann eine dieser Methoden aufrufen, in diesem Fall setParameter:

Statement statement1;
...
// Unwrap the statement1 object to expose the SnowflakeStatement object, and call the
// SnowflakeStatement object's setParameter() method.
statement1.unwrap(SnowflakeStatement.class).setParameter(...);

Ausführen einer asynchronen Abfrage

Der Snowflake-JDBC-Treiber unterstützt asynchrone Abfragen (d. h. Abfragen, die dem Benutzer die Kontrolle zurückgeben, bevor die Abfrage abgeschlossen ist). Benutzer können eine Abfrage starten und dann durch Abrufen feststellen, wann die Abfrage abgeschlossen ist. Nach Abschluss der Abfrage kann der Benutzer das Resultset lesen.

Mit dieser Funktion kann ein Clientprogramm mehrere Abfragen parallel ausführen, ohne dass das Clientprogramm selbst Multithreading verwendet.

Asynchrone Abfragen verwenden Methoden, die den Klassen SnowflakeConnection, SnowflakeStatement, SnowflakePreparedStatement und SnowflakeResultSet hinzugefügt wurden.

Sie können in derselben Sitzung eine Mischung aus synchronen und asynchronen Abfragen ausführen.

Best Practices für asynchrone Abfragen

  • Stellen Sie vor der parallelen Ausführung von Abfragen sicher, dass Sie die Abhängigkeiten der Abfragen von anderen Abfragen genau kennen. Einige Abfragen sind voneinander und von der Ausführungsreihenfolge abhängig und sind daher nicht für die Parallelisierung geeignet. Beispielsweise kann eine INSERT-Anweisung offensichtlich erst beginnen, nachdem die entsprechende CREATE TABLE-Anweisung abgeschlossen wurde.

  • Stellen Sie sicher, dass die Anzahl der gestarteten Abfragen auf den verfügbaren Arbeitsspeicher abgestimmt ist. Das parallele Ausführen mehrerer Abfragen beansprucht normalerweise mehr Speicher, insbesondere wenn mehr als ein ResultSet gleichzeitig im Arbeitsspeicher gespeichert ist.

  • Behandeln Sie beim Abrufen die seltenen Fälle, in denen eine Abfrage nicht erfolgreich ist. Vermeiden Sie beispielsweise die folgende potenzielle Endlosschleife:

    QueryStatus queryStatus = QueryStatus.RUNNING;
    while (queryStatus != QueryStatus.SUCCESS)  {     //  NOT RECOMMENDED
        Thread.sleep(2000);   // 2000 milliseconds.
        queryStatus = resultSet.unwrap(SnowflakeResultSet.class).getStatus();
        }
    

    Verwenden Sie stattdessen ungefähr folgenden Code:

    // Assume that the query isn't done yet.
    QueryStatus queryStatus = QueryStatus.RUNNING;
    while (queryStatus == QueryStatus.RUNNING || queryStatus == QueryStatus.RESUMING_WAREHOUSE)  {
        Thread.sleep(2000);   // 2000 milliseconds.
        queryStatus = resultSet.unwrap(SnowflakeResultSet.class).getStatus();
        }
    
    if (queryStatus == QueryStatus.SUCCESS) {
        ...
        }
    
  • Stellen Sie sicher, dass Anweisungen zur Transaktionssteuerung (BEGIN, COMMIT und ROLLBACK) nicht parallel zu anderen Anweisungen ausgeführt werden.

Beispiele für asynchrone Abfragen

In den meisten dieser Beispiele muss das Programm die folgenden Klassen importieren:

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import net.snowflake.client.core.QueryStatus;
import net.snowflake.client.jdbc.SnowflakeConnection;
import net.snowflake.client.jdbc.SnowflakeResultSet;
import net.snowflake.client.jdbc.SnowflakeStatement;
import veritas_classes.JDBC_veritas_base; // Used for testing. Customers should not and cannot use

// this.

Dies ist ein sehr einfaches Beispiel:

    String sql_command = "";
    ResultSet resultSet;

    System.out.println("Create JDBC statement.");
    Statement statement = connection.createStatement();
    sql_command = "SELECT PI()";
    System.out.println("Simple SELECT query: " + sql_command);
    resultSet = statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(sql_command);

    // Assume that the query isn't done yet.
    QueryStatus queryStatus = QueryStatus.RUNNING;
    while (queryStatus == QueryStatus.RUNNING || queryStatus == QueryStatus.RESUMING_WAREHOUSE) {
      Thread.sleep(2000); // 2000 milliseconds.
      queryStatus = resultSet.unwrap(SnowflakeResultSet.class).getStatus();
    }

    if (queryStatus == QueryStatus.FAILED_WITH_ERROR) {
      // Print the error code to stdout
      System.out.format("Error code: %d%n", queryStatus.getErrorCode());
      System.out.format("Error message: %s%n", queryStatus.getErrorMessage());
    } else if (queryStatus != QueryStatus.SUCCESS) {
      System.out.println("ERROR: unexpected QueryStatus: " + queryStatus);
    } else {
      boolean result_exists = resultSet.next();
      if (!result_exists) {
        System.out.println("ERROR: No rows returned.");
      } else {
        float pi_result = resultSet.getFloat(1);
        System.out.println("pi = " + pi_result);
      }
    }

In diesem Beispiel wird die Abfrage-ID gespeichert, die Verbindung geschlossen, die Verbindung erneut geöffnet und die Daten werden mithilfe der Abfrage-ID abgerufen:

    String sql_command = "";
    ResultSet resultSet;
    String queryID = "";

    System.out.println("Create JDBC statement.");
    Statement statement = connection.createStatement();
    sql_command = "SELECT PI() * 2";
    System.out.println("Simple SELECT query: " + sql_command);
    resultSet = statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(sql_command);
    queryID = resultSet.unwrap(SnowflakeResultSet.class).getQueryID();
    System.out.println("INFO: Closing statement.");
    statement.close();
    System.out.println("INFO: Closing connection.");
    connection.close();

    System.out.println("INFO: Re-opening connection.");
    connection = create_connection(args);
    use_warehouse_db_and_schema(connection);
    resultSet = connection.unwrap(SnowflakeConnection.class).createResultSet(queryID);

    // Assume that the query isn't done yet.
    QueryStatus queryStatus = QueryStatus.RUNNING;
    while (queryStatus == QueryStatus.RUNNING) {
      Thread.sleep(2000); // 2000 milliseconds.
      queryStatus = resultSet.unwrap(SnowflakeResultSet.class).getStatus();
    }

    if (queryStatus == QueryStatus.FAILED_WITH_ERROR) {
      System.out.format(
          "ERROR %d: %s%n", queryStatus.getErrorMessage(), queryStatus.getErrorCode());
    } else if (queryStatus != QueryStatus.SUCCESS) {
      System.out.println("ERROR: unexpected QueryStatus: " + queryStatus);
    } else {
      boolean result_exists = resultSet.next();
      if (!result_exists) {
        System.out.println("ERROR: No rows returned.");
      } else {
        float pi_result = resultSet.getFloat(1);
        System.out.println("pi = " + pi_result);
      }
    }

Datendateien direkt von einem Stream in einen internen Stagingbereich hochladen

Sie können Datendateien mit dem PUT-Befehl hochladen. Manchmal ist es jedoch sinnvoll, Daten direkt von einem Stream in einen internen (d. h. Snowflake) Stagingbereich als Datei zu übertragen. Hier wird die Methode in der Klasse SnowflakeConnection angezeigt:

/**
 * Method to compress data from a stream and upload it at a stage location.
 * The data will be uploaded as one file. No splitting is done in this method.
 *
 * Caller is responsible for releasing the inputStream after the method is
 * called.
 *
 * @param stageName    stage name: e.g. ~ or table name or stage name
 * @param destPrefix   path / prefix under which the data should be uploaded on the stage
 * @param inputStream  input stream from which the data will be uploaded
 * @param destFileName destination file name to use
 * @param compressData compress data or not before uploading stream
 * @throws java.sql.SQLException failed to compress and put data from a stream at stage
 */
public void uploadStream(String stageName,
                         String destPrefix,
                         InputStream inputStream,
                         String destFileName,
                         boolean compressData)
    throws SQLException

Anwendung des Beispiels:

Connection connection = DriverManager.getConnection(url, prop);
File file = new File("/tmp/test.csv");
FileInputStream fileInputStream = new FileInputStream(file);

// upload file stream to user stage
connection.unwrap(SnowflakeConnection.class).uploadStream("MYSTAGE", "testUploadStream",
   fileInputStream, "destFile.csv", true);

Code, der für JDBC-Treiberversionen vor 3.9.2 geschrieben wurde, wird möglicherweise in SnowflakeConnectionV1 umgewandelt und nicht in SnowflakeConnection.class entpackt. Beispiel:

...

// For versions prior to 3.9.2:
// upload file stream to user stage
((SnowflakeConnectionV1) connection.uploadStream("MYSTAGE", "testUploadStream",
   fileInputStream, "destFile.csv", true));

Bemerkung

Kunden, die neuere Versionen des Treibers verwenden, sollten ihren Code für die Verwendung von unwrap aktualisieren.

Datendateien direkt von einem internen Stagingbereich in einen Stream herunterladen

Sie können Datendateien mit dem GET-Befehl herunterladen. Manchmal ist es jedoch sinnvoll, Daten direkt aus einer Datei in einen internen (d. h. Snowflake) Stagingbereich zu übertragen. Hier wird die Methode in der Klasse SnowflakeConnection angezeigt:

/**
 * Download file from the given stage and return an input stream
 *
 * @param stageName      stage name
 * @param sourceFileName file path in stage
 * @param decompress     true if file compressed
 * @return an input stream
 * @throws SnowflakeSQLException if any SQL error occurs.
 */
InputStream downloadStream(String stageName,
                           String sourceFileName,
                           boolean decompress) throws SQLException;

Anwendung des Beispiels:

Connection connection = DriverManager.getConnection(url, prop);
InputStream out = connection.unwrap(SnowflakeConnection.class).downloadStream(
    "~",
    DEST_PREFIX + "/" + TEST_DATA_FILE + ".gz",
    true);

Code, der für JDBC-Treiberversionen vor 3.9.2 geschrieben wurde, wird möglicherweise in SnowflakeConnectionV1 umgewandelt und nicht in SnowflakeConnection.class entpackt. Beispiel:

...

// For versions prior to 3.9.2:
// download file stream to user stage
((SnowflakeConnectionV1) connection.downloadStream(...));

JDBC mit mehreren Anweisungen

In diesem Abschnitt wird beschrieben, wie Sie mit JDBC-Treiber mehrere Anweisungen in einer einzelnen Anforderung ausführen.

Bemerkung

Standardmäßig gibt Snowflake bei Abfragen mit mehreren Anweisungen einen Fehler zurück. Dieses Verhalten dient teilweise zum Schutz vor einer Einschleusung von SQL-Befehlen. Die Verwendung der Funktion für mehrere Anweisungen eröffnet die Möglichkeit der Einschleusung von SQL-Befehlen und sollte mit größter Sorgfalt verwendet werden. Das Risiko kann verringert werden, indem mit der setParameter()-Methode der SnowflakeStatement-Klasse die Anzahl der auszuführenden Anweisungen angegeben wird. Dies erschwert das Einfügen einer Anweisung durch Anhängen. Weitere Details dazu finden Sie unter Schnittstelle: SnowflakeStatement.

Mehrere Anweisungen senden und Ergebnisse verarbeiten

Abfragen, die mehrere Anweisungen enthalten, können auf dieselbe Weise wie Abfragen mit einer einzelnen Anweisung ausgeführt werden, mit der Ausnahme, dass die Abfragezeichenfolge mehrere durch Semikolons getrennte Anweisungen enthält.

Es gibt zwei Möglichkeiten, mehrere Anweisungen zuzulassen:

  • Rufen Sie Statement.setParameter („MULTI_STATEMENT_COUNT“, n) auf, um anzugeben, wie viele Anweisungen gleichzeitig ausgeführt werden dürfen. Weitere Details dazu finden Sie unten.

  • Legen Sie den Parameter MULTI_STATEMENT_COUNT auf Sitzungsebene oder Kontoebene fest, indem Sie einen der folgenden Befehle ausführen:

    alter session set MULTI_STATEMENT_COUNT = 0;
    

    Oder:

    alter account <account> set MULTI_STATEMENT_COUNT = 0;
    

    Wenn Sie den Parameter auf 0 setzen, wird eine unbegrenzte Anzahl von Anweisungen zugelassen. Wenn Sie den Parameter auf 1 setzen, wird jeweils nur eine Anweisung zugelassen.

Um Angriffe durch Einschleusung von SQL-Befehlen zu erschweren, können Benutzer mithilfe der setParameter-Methode die Anzahl der Anweisungen angeben, die durch einen einzelnen Aufruf ausgeführt werden sollen (siehe unten). In diesem Beispiel beträgt die Anzahl der Anweisungen, die durch einen einzelnen Aufruf ausgeführt werden sollen, 3:

// Specify the number of statements that we expect to execute.
statement.unwrap(SnowflakeStatement.class).setParameter(
        "MULTI_STATEMENT_COUNT", 3);

Die Standardanzahl der Anweisungen ist 1. Mit anderen Worten, der Mehrfachanweisungsmodus ist deaktiviert.

Übergeben Sie den Wert 0, um mehrere Anweisungen auszuführen, ohne die genaue Anzahl anzugeben.

Der Parameter MULTI_STATEMENT_COUNT ist nicht Teil des JDBC-Standards, sondern eine Snowflake-Erweiterung. Dieser Parameter betrifft mehr als einen Snowflake-Treiber oder -Konnektor.

Wenn in einer einzigen execute()-Anweisung mehrere Anweisungen ausgeführt werden, ist das Ergebnis der ersten Anweisung über die Standardmethoden getResultSet() und getUpdateCount() verfügbar. Verwenden Sie die Methode getMoreResults(), um auf die Ergebnisse der nachfolgenden Anweisungen zuzugreifen. Diese Methode gibt true zurück, wenn das nächste Ergebnis ein Resultset ist, und false, wenn das nächste Ergebnis eine Aktualisierungszahl ist (oder wenn keine weiteren Ergebnisse vorliegen).

Im folgenden Beispiel wird der Parameter MULTI_STATEMENT_COUNT festgelegt, dann werden 3 Anweisungen ausgeführt und schließlich werden die Aktualisierungszähler und die Resultsets abgerufen:

// Create a string that contains multiple SQL statements.
String command_string = "create table test(n int); " +
                        "insert into test values (1), (2); " +
                        "select * from test order by n";
Statement stmt = connection.createStatement();
// Specify the number of statements (3) that we expect to execute.
stmt.unwrap(SnowflakeStatement.class).setParameter(
        "MULTI_STATEMENT_COUNT", 3);

// Execute all of the statements.
stmt.execute(command_string);                       // false

// --- Get results. ---
// First statement (create table)
stmt.getUpdateCount();                              // -1 (DDL)

// Second statement (insert)
stmt.getMoreResults();                              // false
stmt.getUpdateCount();                              // 2

// Third statement (select)
stmt.getMoreResults();                              // true
ResultSet rs = stmt.getResultSet();
rs.next();                                          // true
rs.getInt(1);                                       // 1
rs.next();                                          // true
rs.getInt(1);                                       // 2
rs.next();                                          // false

// Past the last statement executed.
stmt.getMoreResults();                              // false
stmt.getUpdateCount();                              // -1 (no more results)

Snowflake empfiehlt die Verwendung von execute() für Abfragen mit mehreren Anweisungen. Die Methoden executeQuery() und executeUpdate() unterstützen auch mehrere Anweisungen, lösen jedoch eine Ausnahme aus, wenn das erste Ergebnis nicht dem erwarteten Ergebnistyp entspricht (Resultset bzw. Aktualisierungsanzahl).

Fehlgeschlagene Anweisungen

Wenn eine der SQL-Anweisungen nicht kompiliert oder ausgeführt werden kann, wird die Ausführung abgebrochen. Alle vorherigen Anweisungen, die zuvor ausgeführt wurden, bleiben davon unberührt.

Im folgenden Beispiel werden Anweisungen als einzelne Abfrage mit mehreren Anweisungen ausgeführt. Dabei schlägt die Abfrage bei der dritten Anweisung fehl, und es wird eine Ausnahme ausgelöst.

CREATE OR REPLACE TABLE test(n int);
INSERT INTO TEST VALUES (1), (2);
INSERT INTO TEST VALUES ('not_an_int');  -- execution fails here
INSERT INTO TEST VALUES (3);

Wenn Sie dann den Inhalt der Tabelle test abfragen würden, wären die Werte 1 und 2 vorhanden.

Nicht unterstützte Funktionen

PUT- und GET-Anweisungen werden für Abfragen mit mehreren Anweisungen nicht unterstützt.

Das Vorbereiten von Anweisungen und die Verwendung von Bindungsvariablen werden für Abfragen mit mehreren Anweisungen ebenfalls nicht unterstützt.

Batcheinfügungen

In Ihrem Java-Anwendungscode können Sie mehrere Zeilen in einen einzelnen Batch einfügen, indem Sie Parameter in einer INSERT-Anweisung binden und dann addBatch() und executeBatch() aufrufen.

Der folgende Code fügt beispielsweise zwei Zeilen in eine Tabelle ein, die eine INTEGER-Spalte und eine VARCHAR-Spalte enthält. Im Beispiel werden Werte an die Parameter der INSERT-Anweisung gebunden und dann addBatch() und executeBatch() aufgerufen, um eine Batcheinfügung durchzuführen.

Connection connection = DriverManager.getConnection(url, prop);
connection.setAutoCommit(false);

PreparedStatement pstmt = connection.prepareStatement("INSERT INTO t(c1, c2) VALUES(?, ?)");
pstmt.setInt(1, 101);
pstmt.setString(2, "test1");
pstmt.addBatch();

pstmt.setInt(1, 102);
pstmt.setString(2, "test2");
pstmt.addBatch();

int[] count = pstmt.executeBatch(); // After execution, count[0]=1, count[1]=1
connection.commit();

Wenn Sie dieses Verfahren verwenden, um eine große Anzahl von Werten einzufügen, kann die Treiberleistung verbessert werden, indem er die Daten zur Erfassung an einen temporären Stagingbereich gesendet werden. Der Treiber führt dies automatisch durch, wenn die Anzahl der Werte einen Schwellenwert überschreitet.

Damit der Treiber die Daten an einen temporären Stagingbereich senden kann, muss der Benutzer über die folgende Berechtigung für das Schema verfügen:

  • CREATE STAGE.

Wenn der Benutzer nicht über diese Berechtigung verfügt, sendet der Treiber die Daten mit der Abfrage an die Snowflake-Datenbank zurück.

Außerdem müssen die aktuelle Datenbank und das aktuelle Schema für die Sitzung festgelegt sein. Wenn diese nicht festgelegt sind, kann der vom Treiber ausgeführte CREATE TEMPORARY STAGE-Befehl folgenden Fehler generieren:

CREATE TEMPORARY STAGE SYSTEM$BIND file_format=(type=csv field_optionally_enclosed_by='"')
Cannot perform CREATE STAGE. This session does not have a current schema. Call 'USE SCHEMA', or use a qualified name.

Bemerkung

Alternative Möglichkeiten zum Laden von Daten in die Snowflake-Datenbank (einschließlich Massenladen mit dem COPY-Befehl) finden Sie unter Laden von Daten in Snowflake.

Java-Beispielprogramm

Für ein in Java geschriebenes Beispiel klicken Sie mit der rechten Maustaste auf den Namen der Datei SnowflakeJDBCExample.java, und speichern Sie den Link bzw. die Datei in Ihrem lokalen Dateisystem.