W3docs

Procesamiento por lotes JDBC en Java

Ejecuta múltiples sentencias SQL de forma eficiente en Java con JDBC batch processing — addBatch y executeBatch.

Cuando tienes cientos o miles de inserciones o actualizaciones por ejecutar, enviarlas de una en una implica un viaje de red de ida y vuelta por cada una — el costo dominante. El procesamiento por lotes reúne muchas sentencias y las envía a la base de datos en un único viaje, convirtiendo con frecuencia segundos en milisegundos. Es la técnica estándar para la carga masiva de datos.

Este capítulo explica cómo encolar sentencias con addBatch() y ejecutarlas con executeBatch(), qué significa el int[] devuelto (incluidos sus dos marcadores especiales), cómo envolver un lote en una transacción y cómo recuperarse cuando falla una sentencia. Se basa en JDBC PreparedStatement y JDBC Transactions.

addBatch y executeBatch

Encolas sentencias con addBatch() y las disparas todas con executeBatch(), que devuelve un int[] con el número de filas afectadas por cada sentencia:

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 vinculas parámetros y llamas a addBatch() por fila; con un Statement plano pasas una cadena SQL completa a addBatch(sql). La forma preparada es preferida — mismas ventajas de seguridad en el enlace de parámetros (sin inyección SQL) y reutilización del plan de ejecución de siempre. Ten en cuenta que un solo lote de PreparedStatement debe ejecutar una cadena SQL fija con parámetros variables; si necesitas sentencias genuinamente distintas, usa un Statement plano.

El valor de retorno y sus marcadores especiales

executeBatch() devuelve una entrada por cada sentencia en cola. La mayoría son el recuento de filas, pero dos constantes señalan casos especiales:

  • Statement.SUCCESS_NO_INFO (−2): la sentencia se ejecutó correctamente, pero el driver no sabe cuántas filas tocó.
  • Statement.EXECUTE_FAILED (−3): esta sentencia en particular falló (solo se observa a través de BatchUpdateException).

Lote + transacción

Siempre ejecuta un lote dentro de una transacción explícita (setAutoCommit(false)), para que un fallo revierta el lote completo en lugar de dejarlo a medias. Descarga lotes grandes periódicamente — cada ~1000 filas — llamando a executeBatch() y luego clearBatch(), para que la cola en memoria del driver no crezca sin límite:

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 together

Dado que todos los fragmentos comparten una sola transacción, las llamadas periódicas a executeBatch() no confirman nada por sí solas — el commit() al final es lo que hace que toda la carga sea duradera, y un único rollback() deshace todo.

Cuando falla una sentencia del lote

Si alguna sentencia falla, executeBatch() lanza BatchUpdateException. Su método getUpdateCounts() devuelve los recuentos acumulados hasta ese momento — permitiéndote ver qué sentencias se ejecutaron antes del fallo — y contiene los datos habituales de SQLException, como getSQLState().

Un ejemplo completo: recuentos, marcadores y un lote fallido

Este programa construye un lote, imprime las dos constantes de marcador especial, muestra el int[] que devuelve una ejecución limpia y construye una BatchUpdateException para demostrar exactamente qué reporta getUpdateCounts() cuando falla una sentencia — todo sin una base de datos real.

java— editable, runs on the server

Lo que hay que retener de la ejecución:

  • addBatch() encola trabajo sin enviarlo; executeBatch() envía toda la cola en un único viaje de ida y vuelta. La ganancia está puramente en el número de viajes — tres inserciones aquí, pero la misma estructura escala a miles donde el ahorro es enorme.
  • Una ejecución limpia devuelve [1, 1, 1] — un recuento de actualizaciones por sentencia en cola, en orden. Lees este array para confirmar que cada sentencia afectó las filas esperadas.
  • SUCCESS_NO_INFO (−2) significa "funcionó pero no cuento filas". Algunos drivers lo devuelven para sentencias en lote, así que trata cualquier valor negativo que no sea de fallo como éxito, nunca como error.
  • Ante un fallo, el driver lanza BatchUpdateException, y getUpdateCounts() devuelve [1, -3, -2]: la primera inserción tuvo éxito, la segunda falló (EXECUTE_FAILED = −3), y el comportamiento para el resto depende del driver. Ese array es cómo localizas la sentencia problemática.
  • La excepción contiene un SQLState (23505 es el código estándar de violación de restricción de integridad). Combinado con una transacción envolvente y rollback(), así es como una carga masiva fallida deja la base de datos intacta en lugar de parcialmente escrita.

Práctica

Práctica
Una inserción masiva llama a addBatch() en un bucle y luego a executeBatch(), y una fila viola una restricción de unicidad. ¿Qué hace el driver y cómo encuentras la fila fallida?
Una inserción masiva llama a addBatch() en un bucle y luego a executeBatch(), y una fila viola una restricción de unicidad. ¿Qué hace el driver y cómo encuentras la fila fallida?
Was this page helpful?