Traitement par lots JDBC en Java
Exécutez de nombreuses instructions SQL efficacement en Java avec le traitement par lots JDBC — addBatch et executeBatch.
Lorsque vous devez exécuter des centaines ou des milliers d'insertions ou de mises à jour, les envoyer une par une signifie un aller-retour réseau pour chacune — c'est le coût dominant. Le traitement par lots regroupe de nombreuses instructions et les envoie à la base de données en un seul aller-retour, transformant souvent des secondes en millisecondes. C'est la technique standard pour le chargement en masse.
Ce chapitre explique comment mettre des instructions en file d'attente avec addBatch() et les exécuter avec executeBatch(), ce que signifie le tableau int[] retourné (y compris ses deux marqueurs spéciaux), comment envelopper un lot dans une transaction, et comment récupérer lorsqu'une instruction échoue. Il s'appuie sur JDBC PreparedStatement et JDBC Transactions.
addBatch et executeBatch
Vous mettez des instructions en file d'attente avec addBatch() et les exécutez toutes avec executeBatch(), qui retourne un tableau int[] de comptages de mises à jour par instruction :
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
}Avec un PreparedStatement, vous liez les paramètres et appelez addBatch() par ligne ; avec un Statement ordinaire, vous passez une chaîne SQL complète à addBatch(sql). La forme préparée est préférée — mêmes avantages de sécurité des paramètres (pas d'injection SQL) et de réutilisation du plan qu'à l'accoutumée. Notez qu'un lot PreparedStatement unique doit exécuter une chaîne SQL fixe avec des paramètres variables ; si vous avez besoin d'instructions vraiment différentes, utilisez un Statement ordinaire.
La valeur de retour et ses marqueurs spéciaux
executeBatch() retourne une entrée par instruction mise en file d'attente. La plupart sont le nombre de lignes, mais deux constantes signalent des cas particuliers :
Statement.SUCCESS_NO_INFO(−2) : l'instruction a réussi, mais le pilote ne sait pas combien de lignes elle a affectées.Statement.EXECUTE_FAILED(−3) : cette instruction particulière a échoué (visible uniquement viaBatchUpdateException).
Lot + transaction
Exécutez toujours un lot dans une transaction explicite (setAutoCommit(false)), afin qu'un échec annule l'ensemble du lot plutôt que de le laisser à moitié appliqué. Videz les grands lots périodiquement — toutes les ~1000 lignes — en appelant executeBatch() puis clearBatch(), afin que la file d'attente en mémoire du pilote ne croisse pas sans 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 togetherComme tous les blocs partagent une seule transaction, les appels périodiques à executeBatch() ne valident rien par eux-mêmes — c'est commit() à la fin qui rend l'ensemble du chargement durable, et un seul rollback() annule tout.
Quand une instruction du lot échoue
Si une instruction échoue, executeBatch() lève BatchUpdateException. Sa méthode getUpdateCounts() retourne les comptages collectés jusqu'à présent — vous permettant de voir quelles instructions ont été exécutées avant l'échec — et elle contient les données habituelles de SQLException comme getSQLState().
Exemple complet : comptages, marqueurs et lot échoué
Ce programme construit un lot, affiche les deux constantes de marqueurs spéciaux, montre le tableau int[] qu'un exécution réussie retourne, et construit une BatchUpdateException pour démontrer exactement ce que getUpdateCounts() rapporte lorsqu'une instruction échoue — tout cela sans base de données active.
Ce qu'il faut retenir de l'exécution :
addBatch()met le travail en file d'attente sans l'envoyer ;executeBatch()envoie toute la file en un seul aller-retour. Le gain est purement dans le nombre d'allers-retours — trois insertions ici, mais le même schéma passe à l'échelle sur des milliers où l'économie est énorme.- Une exécution réussie retourne
[1, 1, 1]— un comptage de mises à jour par instruction mise en file d'attente, dans l'ordre. Vous lisez ce tableau pour confirmer que chaque instruction a affecté les lignes attendues. SUCCESS_NO_INFO(−2) signifie « ça a marché mais je ne compte pas les lignes ». Certains pilotes le retournent pour les instructions en lot, donc traitez toute valeur négative mais non échouée comme un succès, jamais comme une erreur.- En cas d'échec, le pilote lève
BatchUpdateException, etgetUpdateCounts()retourne[1, -3, -2]: la première insertion a réussi, la seconde a échoué (EXECUTE_FAILED= −3), et le comportement pour le reste est défini par le pilote. Ce tableau est la façon de localiser l'instruction fautive. - L'exception contient un
SQLState(23505est le code standard de violation de contrainte d'intégrité). Combiné à une transaction englobante etrollback(), c'est ainsi qu'un chargement en masse échoué laisse la base de données intacte plutôt qu'à moitié écrite.