Java: inserisci più righe in MySQL con PreparedStatement


88

Voglio inserire più righe in una tabella MySQL contemporaneamente usando Java. Il numero di righe è dinamico. In passato stavo facendo ...

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

    myStatement.executeUpdate();
}

Vorrei ottimizzarlo per utilizzare la sintassi supportata da MySQL:

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

ma con un PreparedStatementnon so come farlo poiché non so in anticipo quanti elementi arrayconterrà. Se non è possibile con a PreparedStatement, in quale altro modo posso farlo (e comunque sfuggire ai valori nell'array)?

Risposte:


178

Puoi creare un batch da PreparedStatement#addBatch()ed eseguirlo da PreparedStatement#executeBatch().

Ecco un esempio di kickoff:

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.
            }
        }
    }
}

Viene eseguito ogni 1000 elementi perché alcuni driver JDBC e / o DB potrebbero avere una limitazione sulla lunghezza del batch.

Vedi anche :


26
I tuoi inserti andranno più veloci se li metti nelle transazioni ... cioè avvolgi connection.setAutoCommit(false);e connection.commit(); scarica.oracle.com/javase/tutorial/jdbc/basics/…
Joshua Martell

1
Sembra che tu possa eseguire un batch vuoto se ci sono 999 articoli.
Djechlin

2
@electricalbah verrà eseguito normalmente perchéi == entities.size()
Yohanes AI

Ecco un'altra buona risorsa per mettere insieme lavori batch utilizzando istruzioni preparate. viralpatel.net/blogs/batch-insert-in-java-jdbc
Danny Bullis

1
@ AndréPaulo: solo qualsiasi SQL INSERT adatto per un'istruzione preparata. Fare riferimento ai collegamenti al tutorial JDBC per esempi di base. Questo non è legato alla domanda concreta.
BalusC

32

Quando viene utilizzato il driver MySQL, è necessario impostare il parametro di connessione rewriteBatchedStatementssu true ( jdbc:mysql://localhost:3306/TestDB?**rewriteBatchedStatements=true**).

Con questo parametro l'istruzione viene riscritta per l'inserimento in blocco quando la tabella è bloccata solo una volta e gli indici vengono aggiornati solo una volta. Quindi è molto più veloce.

Senza questo parametro l'unico vantaggio è un codice sorgente più pulito.


questo è un commento per le prestazioni per la costruzione: statement.addBatch (); if ((i + 1)% 1000 == 0) {statement.executeBatch (); // Esegui ogni 1000 elementi. }
MichalSv

Apparentemente il driver MySQL ha un bug bugs.mysql.com/bug.php?id=71528 Ciò causa anche problemi per framework ORM come Hibernate hibernate.atlassian.net/browse/HHH-9134
Shailendra

Sì. Anche per ora è corretto. Almeno per la 5.1.45versione del connettore mysql.
v.ladynev

<artifactId> mysql-connector-java </artifactId> <version> 8.0.14 </version> Ho appena controllato che sia corretto 8.0.14. Senza aggiungere rewriteBatchedStatements=truenon c'è guadagno di prestazioni.
vincent mathew

8

Se è possibile creare dinamicamente l'istruzione sql, è possibile eseguire la seguente soluzione alternativa:

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

Credo che la risposta accettata sia di gran lunga migliore !! Non sapevo degli aggiornamenti in batch e quando ho iniziato a scrivere questa risposta quella risposta non è stata ancora inviata !!! :)
Ali Shakiba

Questo approccio è molto più veloce di quello accettato. Lo provo, ma non trovo il motivo. @ JohnS sai perché?
julian0zzx

@ julian0zzx no, ma forse perché viene eseguito come sql singolo invece che multiplo. ma non sono sicuro.
Ali Shakiba

3

Nel caso in cui si dispone dell'incremento automatico nella tabella e si necessita di accedervi .. è possibile utilizzare il seguente approccio ... Eseguire un test prima dell'uso perché getGeneratedKeys () in Statement perché dipende dal driver utilizzato. Il codice seguente è testato su Maria DB 10.0.12 e sul driver Maria JDBC 1.2

Ricorda che l'aumento della dimensione del batch migliora le prestazioni solo in una certa misura ... per la mia configurazione, l'aumento della dimensione del batch oltre 500 stava effettivamente degradando le prestazioni.

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

3

@Ali Shakiba il tuo codice necessita di alcune modifiche. Parte errore:

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

Codice aggiornato:

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

Questo è un approccio molto più veloce e migliore nell'intera risposta. Questa dovrebbe essere la risposta accettata
Arun Shankar

1
Come menzionato nella risposta accettata, alcuni driver / database JDBC hanno limiti sul numero di righe che è possibile includere in un'istruzione INSERT. Nel caso dell'esempio precedente, se myArrayha una lunghezza maggiore di quel limite, si verificherà un'eccezione. Nel mio caso, ho un limite di 1.000 righe che suscita la necessità di un'esecuzione batch, perché potrei potenzialmente aggiornare più di 1.000 righe in una determinata esecuzione. Questo tipo di istruzione dovrebbe teoricamente funzionare bene se sai che stai inserendo meno del massimo consentito. Qualcosa da tenere a mente.
Danny Bullis

Per chiarire, la risposta sopra menziona le limitazioni del driver / database JDBC sulla lunghezza del batch, ma potrebbero anche esserci limiti al numero di righe incluse in un'istruzione di inserimento, come ho visto nel mio caso.
Danny Bullis

0

possiamo inviare più aggiornamenti insieme in JDBC per inviare aggiornamenti in batch.

possiamo usare gli oggetti Statement, PreparedStatement e CallableStatement per l'aggiornamento del bacth con disabilitare autocommit

Le funzioni addBatch () ed executeBatch () sono disponibili con tutti gli oggetti istruzione per avere BatchUpdate

qui il metodo addBatch () aggiunge una serie di istruzioni o parametri al batch corrente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.