Java: Fügen Sie mit PreparedStatement mehrere Zeilen in MySQL ein

87

Ich möchte mit Java mehrere Zeilen gleichzeitig in eine MySQL-Tabelle einfügen. Die Anzahl der Zeilen ist dynamisch. In der Vergangenheit habe ich ...

for (String element : array) {
    myStatement.setString(1, element[0]);
    myStatement.setString(2, element[1]);

    myStatement.executeUpdate();
}

Ich möchte dies optimieren, um die von MySQL unterstützte Syntax zu verwenden:

INSERT INTO table (col1, col2) VALUES ('val1', 'val2'), ('val1', 'val2')[, ...]

aber mit a PreparedStatementweiß ich keine Möglichkeit, dies zu tun, da ich vorher nicht weiß, wie viele Elemente arrayenthalten werden. Wenn es mit a nicht möglich ist PreparedStatement, wie kann ich es sonst tun (und trotzdem die Werte im Array maskieren)?

Tom Marthenal
quelle

Antworten:

175

Sie können einen Stapel von erstellen PreparedStatement#addBatch()und ausführen von PreparedStatement#executeBatch().

Hier ist ein Kickoff-Beispiel:

public void save(List<Entity> entities) throws SQLException {
    try (
        Connection connection = database.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL_INSERT);
    ) {
        int i = 0;

        for (Entity entity : entities) {
            statement.setString(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
            i++;

            if (i % 1000 == 0 || i == entities.size()) {
                statement.executeBatch(); // Execute every 1000 items.
            }
        }
    }
}

Es wird alle 1000 Elemente ausgeführt, da einige JDBC-Treiber und / oder DBs möglicherweise eine Einschränkung der Stapellänge aufweisen.

Siehe auch :

BalusC
quelle
26
Ihre Einfügungen werden schneller, wenn Sie sie in Transaktionen einfügen ... dh einpacken mit connection.setAutoCommit(false);und connection.commit(); download.oracle.com/javase/tutorial/jdbc/basics/…
Joshua Martell
1
Sieht so aus, als könnten Sie einen leeren Stapel ausführen, wenn 999 Artikel vorhanden sind.
Djechlin
2
@electricalbah wird es normal ausgeführt, weili == entities.size()
Yohanes AI
Hier ist eine weitere gute Ressource zum Zusammenstellen von Stapeljobs mithilfe vorbereiteter Anweisungen. viralpatel.net/blogs/batch-insert-in-java-jdbc
Danny Bullis
1
@ AndréPaulo: Nur jedes SQL INSERT, das für eine vorbereitete Anweisung geeignet ist. Grundlegende Beispiele finden Sie in den Links zum JDBC-Lernprogramm. Dies hängt nicht mit der konkreten Frage zusammen.
BalusC
30

Wenn der MySQL-Treiber verwendet wird, müssen Sie den Verbindungsparameter rewriteBatchedStatementsauf true setzen ( jdbc:mysql://localhost:3306/TestDB?**rewriteBatchedStatements=true**).

Mit diesem Parameter wird die Anweisung in Masseneinfügung umgeschrieben, wenn die Tabelle nur einmal gesperrt und die Indizes nur einmal aktualisiert werden. Es geht also viel schneller.

Ohne diesen Parameter ist nur sauberer Quellcode von Vorteil.

MichalSv
quelle
Dies ist ein Kommentar für die Leistung für die Konstruktion: statement.addBatch (); if ((i + 1)% 1000 == 0) {statement.executeBatch (); // Alle 1000 Elemente ausführen. }
MichalSv
Anscheinend hat der MySQL-Treiber einen Fehler bugs.mysql.com/bug.php?id=71528 Dies verursacht auch Probleme für ORM-Frameworks wie Hibernate hibernate.atlassian.net/browse/HHH-9134
Shailendra
Ja. Dies ist auch vorerst richtig. Zumindest für die 5.1.45MySQL-Connector-Version.
v.ladynev
<artifactId> mysql-connector-java </ifactId> <version> 8.0.14 </ version> Ich habe gerade überprüft, ob 8.0.14 korrekt ist. Ohne Hinzufügen rewriteBatchedStatements=truegibt es keinen Leistungsgewinn.
Vincent Mathew
7

Wenn Sie Ihre SQL-Anweisung dynamisch erstellen können, können Sie folgende Problemumgehung durchführen:

String myArray[][] = { { "1-1", "1-2" }, { "2-1", "2-2" }, { "3-1", "3-2" } };

StringBuffer mySql = new StringBuffer("insert into MyTable (col1, col2) values (?, ?)");

for (int i = 0; i < myArray.length - 1; i++) {
    mySql.append(", (?, ?)");
}

myStatement = myConnection.prepareStatement(mySql.toString());

for (int i = 0; i < myArray.length; i++) {
    myStatement.setString(i, myArray[i][1]);
    myStatement.setString(i, myArray[i][2]);
}
myStatement.executeUpdate();
Ali Shakiba
quelle
Ich glaube die akzeptierte Antwort ist weitaus besser !! Ich wusste nichts über Batch-Updates und als ich anfing, diese Antwort zu schreiben, wurde diese Antwort noch nicht übermittelt !!! :)
Ali Shakiba
Dieser Ansatz ist viel schneller als der akzeptierte. Ich teste es, finde aber nicht warum. @ Johns weißt du warum?
Julian0zzx
@ julian0zzx nein, aber vielleicht, weil es als einzelne SQL anstelle von mehreren ausgeführt wird. aber ich bin mir nicht sicher.
Ali Shakiba
3

Falls Sie ein automatisches Inkrementieren in der Tabelle haben und darauf zugreifen müssen, können Sie den folgenden Ansatz verwenden ... Führen Sie einen Test vor der Verwendung durch, da getGeneratedKeys () in der Anweisung verwendet wird, da dies vom verwendeten Treiber abhängt. Der folgende Code wurde auf Maria DB 10.0.12 und Maria JDBC-Treiber 1.2 getestet

Denken Sie daran, dass das Erhöhen der Stapelgröße die Leistung nur bis zu einem gewissen Grad verbessert. Bei meinem Setup hat das Erhöhen der Stapelgröße über 500 die Leistung tatsächlich beeinträchtigt.

public Connection getConnection(boolean autoCommit) throws SQLException {
    Connection conn = dataSource.getConnection();
    conn.setAutoCommit(autoCommit);
    return conn;
}

private void testBatchInsert(int count, int maxBatchSize) {
    String querySql = "insert into batch_test(keyword) values(?)";
    try {
        Connection connection = getConnection(false);
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        boolean success = true;
        int[] executeResult = null;
        try {
            pstmt = connection.prepareStatement(querySql, Statement.RETURN_GENERATED_KEYS);
            for (int i = 0; i < count; i++) {
                pstmt.setString(1, UUID.randomUUID().toString());
                pstmt.addBatch();
                if ((i + 1) % maxBatchSize == 0 || (i + 1) == count) {
                    executeResult = pstmt.executeBatch();
                }
            }
            ResultSet ids = pstmt.getGeneratedKeys();
            for (int i = 0; i < executeResult.length; i++) {
                ids.next();
                if (executeResult[i] == 1) {
                    System.out.println("Execute Result: " + i + ", Update Count: " + executeResult[i] + ", id: "
                            + ids.getLong(1));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            success = false;
        } finally {
            if (rs != null) {
                rs.close();
            }
            if (pstmt != null) {
                pstmt.close();
            }
            if (connection != null) {
                if (success) {
                    connection.commit();
                } else {
                    connection.rollback();
                }
                connection.close();
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
Gladiator
quelle
3

@Ali Shakiba Ihr Code muss geändert werden. Fehlerteil:

for (int i = 0; i < myArray.length; i++) {
     myStatement.setString(i, myArray[i][1]);
     myStatement.setString(i, myArray[i][2]);
}

Aktualisierter Code:

String myArray[][] = {
    {"1-1", "1-2"},
    {"2-1", "2-2"},
    {"3-1", "3-2"}
};

StringBuffer mySql = new StringBuffer("insert into MyTable (col1, col2) values (?, ?)");

for (int i = 0; i < myArray.length - 1; i++) {
    mySql.append(", (?, ?)");
}

mysql.append(";"); //also add the terminator at the end of sql statement
myStatement = myConnection.prepareStatement(mySql.toString());

for (int i = 0; i < myArray.length; i++) {
    myStatement.setString((2 * i) + 1, myArray[i][1]);
    myStatement.setString((2 * i) + 2, myArray[i][2]);
}

myStatement.executeUpdate();
vinay
quelle
Dies ist ein viel schnellerer und besserer Ansatz für die gesamte Antwort. Dies sollte die akzeptierte Antwort sein
Arun Shankar
1
Wie in der akzeptierten Antwort erwähnt, ist die Anzahl der Zeilen, die Sie in eine INSERT-Anweisung aufnehmen können, bei einigen JDBC-Treibern / Datenbanken begrenzt. Im obigen Beispiel wird myArrayeine Ausnahme ausgelöst , wenn die Länge größer als diese Grenze ist. In meinem Fall habe ich ein Limit von 1.000 Zeilen, wodurch eine Stapelausführung erforderlich wird, da ich möglicherweise mehr als 1.000 Zeilen in einem bestimmten Lauf aktualisieren kann. Diese Art von Anweisung sollte theoretisch gut funktionieren, wenn Sie wissen, dass Sie weniger als das maximal zulässige einfügen. Etwas zu beachten.
Danny Bullis
Zur Verdeutlichung werden in der obigen Antwort Einschränkungen des JDBC-Treibers / der Datenbank für die Stapellänge erwähnt, es kann jedoch auch Einschränkungen für die Anzahl der in einer Einfügeanweisung enthaltenen Zeilen geben, wie ich in meinem Fall gesehen habe.
Danny Bullis
0

In JDBC können mehrere Updates zusammen gesendet werden, um Stapelaktualisierungen zu senden.

Wir können Statement-, PreparedStatement- und CallableStatement-Objekte für die Aktualisierung von Bacth mit deaktiviertem Autocommit verwenden

Die Funktionen addBatch () und executeBatch () sind für alle Anweisungsobjekte mit BatchUpdate verfügbar

Hier fügt die Methode addBatch () dem aktuellen Stapel eine Reihe von Anweisungen oder Parametern hinzu.

kapil das
quelle