Java JDBC Batch Processing
Esegui molte istruzioni SQL in modo efficiente in Java con il batch processing JDBC — addBatch e executeBatch.
Quando devi eseguire centinaia o migliaia di insert o update, inviarli uno alla volta significa un round trip di rete per ciascuno — il costo dominante. Il batching raccoglie molte istruzioni e le invia al database in un unico round trip, spesso trasformando i secondi in millisecondi. È la tecnica standard per il caricamento massivo.
Questo capitolo spiega come accodare le istruzioni con addBatch() e come eseguirle con executeBatch(), cosa significa l'int[] restituito (inclusi i suoi due marcatori speciali), come racchiudere un batch in una transazione e come gestire il caso in cui un'istruzione fallisca. Si basa su JDBC PreparedStatement e JDBC Transactions.
addBatch e executeBatch
Accodi le istruzioni con addBatch() e le esegui tutte con executeBatch(), che restituisce un int[] con i conteggi degli aggiornamenti per ciascuna istruzione:
String sql = "INSERT INTO log(msg) VALUES (?)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
for (String msg : messages) {
ps.setString(1, msg);
ps.addBatch(); // queue this set of parameters
}
int[] counts = ps.executeBatch(); // one round trip
}Con un PreparedStatement si associano i parametri e si chiama addBatch() per ogni riga; con uno Statement semplice si passa una stringa SQL completa a addBatch(sql). La forma preparata è preferibile — stessa sicurezza nell'associazione dei parametri (nessuna SQL injection) e stessi vantaggi di riutilizzo del piano come sempre. Si noti che un singolo batch di PreparedStatement deve eseguire una stringa SQL fissa con parametri variabili; se hai bisogno di istruzioni genuinamente diverse, usa uno Statement semplice.
Il valore restituito e i suoi marcatori speciali
executeBatch() restituisce un elemento per ogni istruzione accodata. La maggior parte sono conteggi di righe, ma due costanti segnalano casi particolari:
Statement.SUCCESS_NO_INFO(−2): l'istruzione è riuscita, ma il driver non sa quante righe ha interessato.Statement.EXECUTE_FAILED(−3): questa particolare istruzione è fallita (visibile solo tramiteBatchUpdateException).
Batch + transazione
Esegui sempre un batch all'interno di una transazione esplicita (setAutoCommit(false)), in modo che un errore faccia il rollback dell'intero batch anziché lasciarlo applicato a metà. Svuota i batch di grandi dimensioni periodicamente — ogni ~1000 righe — chiamando executeBatch() e poi clearBatch(), in modo che la coda in memoria del driver non cresca senza limite:
conn.setAutoCommit(false);
int n = 0;
for (String msg : messages) {
ps.setString(1, msg);
ps.addBatch();
if (++n % 1000 == 0) {
ps.executeBatch(); // flush a chunk
ps.clearBatch(); // free the queued statements
}
}
ps.executeBatch(); // flush the remainder
conn.commit(); // make every chunk durable togetherPoiché tutti i chunk condividono una transazione, le chiamate periodiche a executeBatch() non eseguono alcun commit autonomamente — è il commit() finale che rende l'intero caricamento durevole, e un singolo rollback() annulla tutto.
Quando un'istruzione nel batch fallisce
Se un'istruzione fallisce, executeBatch() lancia BatchUpdateException. Il suo metodo getUpdateCounts() restituisce i conteggi raccolti fino a quel momento — permettendoti di vedere quali istruzioni sono state eseguite prima del fallimento — e contiene i consueti dati SQLException come getSQLState().
Un esempio pratico: conteggi, marcatori e un batch fallito
Questo programma costruisce un batch, stampa le due costanti di marcatore speciale, mostra l'int[] che una esecuzione pulita restituisce, e costruisce una BatchUpdateException per dimostrare esattamente cosa riporta getUpdateCounts() quando un'istruzione fallisce — tutto senza un database reale.
Cosa ricavare dall'esecuzione:
addBatch()accoda il lavoro senza inviarlo;executeBatch()invia l'intera coda in un unico round trip. Il vantaggio sta esclusivamente nel numero di round trip — tre insert qui, ma la stessa struttura scala a migliaia dove il risparmio è enorme.- Un'esecuzione pulita restituisce
[1, 1, 1]— un conteggio degli aggiornamenti per ogni istruzione accodata, in ordine. Leggi questo array per confermare che ogni istruzione ha interessato le righe che ti aspettavi. SUCCESS_NO_INFO(−2) significa "ha funzionato ma non conto le righe". Alcuni driver lo restituiscono per le istruzioni in batch, quindi tratta qualsiasi valore negativo-ma-non-fallito come successo, mai come errore.- In caso di errore il driver lancia
BatchUpdateException, egetUpdateCounts()restituisce[1, -3, -2]: il primo insert è riuscito, il secondo è fallito (EXECUTE_FAILED= −3), e il comportamento per il resto è definito dal driver. Quell'array è il modo per individuare l'istruzione problematica. - L'eccezione contiene un
SQLState(23505è il codice standard di violazione del vincolo di integrità). Combinato con una transazione circostante erollback(), questo è il modo in cui un caricamento massivo fallito lascia il database intatto anziché parzialmente scritto.