Batch Insert en Java

De ChuWiki

En ocasiones nuestro programa Java recibe datos muy deprisa de alguna fuente de datos (un socket, por ejemplo) y debe insertarlos en base de datos con mucha velocidad. Hacer los insert de uno en uno no es muy eficiente. Para insertar datos muy deprisa tenemos la posibilidad de hacer las inserciones (o update) en "Batch". Veamos un ejemplo simple.


El fuente está escrito abajo, pero también lo tienes en JdbcBatchInsertUpdate.java

El código[editar]

El código de ejemplo puede ser el siguiente

package com.chuidiang.ejemplos;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * Ejemplo de Batch insert y update con JDBC.
 * 
 * Para ejecutar este programa necesitas un connector con la base de datos, como
 * mysql-connector-java-5.1.12.jar, segun el servidor de MySQL que tengas. U otro
 * connector de otra base de datos si tienes otro servidor de base de datos
 * 
 * @author Chuidiang
 */
public class JdbcBatchInsertUpdate {

   public static void main(String[] args) {
      try {
         // Los datos que vamos a introducir
         String[][] datos = { { "pedro", "gomez", "perez" },
               { "juan", "alvarez", "sanchez" },
               { "antonio", "rodriquez", "lopez" } };

         // La conexión a la base de datos
         Class.forName("com.mysql.jdbc.Driver");
         Connection conexion = DriverManager.getConnection(
               "jdbc:mysql://localhost:3306/pruebas", "root", "");

         // El prepared statement para los insert
         PreparedStatement ps = conexion
               .prepareStatement(
                     "insert into persona (nombre,apellido1,apellido2) values (?,?,?)",
                     Statement.RETURN_GENERATED_KEYS);

         // Vamos añadiendo datos y añadiendolos al batch.
         for (String[] dato : datos) {
            ps.setString(1, dato[0]);
            ps.setString(2, dato[1]);
            ps.setString(3, dato[2]);

            ps.addBatch();
         }

         // Ejecutamos el batch, devuelve un array de forma que cada posicion
         // contiene
         // el numero de filas afectadas por cada insert.
         int[] exitos = ps.executeBatch();

         for (int i = 0; i < datos.length; i++) {
            System.out.println("La insercion de " + datos[i][0] + "da "
                  + exitos[i] + " inserciones");
         }

         // Claves que se han generado. Fijate al crear el PreparedStatement que
         // se ha puesto la opcion
         // Statement.RETURN_GENERATED_KEYS
         ResultSet rs = ps.getGeneratedKeys();
         int contador = 0;
         while (rs.next()) {
            System.out.println(datos[contador][0]+" tiene clave "+rs.getInt(1));
            contador++;
         }
         
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}

Una explicación[editar]

Suponemos que la tabla en base de datos se llama persona y solo tiene cuatro columnas: id autoincremental, nombre, apellido1 y apellido2. En la línea String [][] datos simulamos los datos que queremos insertar.

Las dos siguientes líneas en el código abren la conexión con la base de datos, en este caso, MySQL, la base de datos pruebas, con usuario root y password vacía.

En la siguiente línea creamos un PreparedStatement de la forma habitual. Unicamente hay que advertir la opción Statement.RETURN_GENERATED_KEYS que hemos puesto como segundo parámetro. Al ponerlo, más adelante podremos obtener los id que han generado los distintos insert. Si no nos interesan estos id, no es necesario poner esta opción.

En un bucle para cada dato, vamos llamando de la forma habitual a los ps.setString(...) para ir colocando los datos. Al terminar de colocar los datos de una persona, no ejecutamos el PreparedStatement, sino que llamamos a ps.addBatch(). De esta forma, el PreparedStatement va guardando todos los insert que tiene que hacer.

Cuando hayamos colocado todos los datos de todas las personas y llamado cada vez a ps.addBatch(), podemos ejecutar el Batch completo llamando a ps.executeBatch(). Esto hará todos los insert rápidamente.

Un detalle a tener en cuenta es que no podemos acumular en un batch todos los insert que queramos, tenemos la limitación de la memoria. Por ello, es bueno ir añadiendo datos de 100 en 100, por ejemplo, y hacer un executeBatch() cada 100 datos (o cada 1000, o lo que consideremos oportuno para no comernos la memoria).

ps.executeBatch() devuelve un array de int, tantos como insert tengamos. En cada posición del array tendremos el número de filas afectadas por cada insert. En nuestro ejemplo, cada insert inserta un solo registro, por lo que todos los elementos del array tendrían valor 1. Si hacemos updates, por ejemplo, podemos obtener ahí valores distintos (0 si el update no afecta a ninguna fila, 1 si afecta a una, etc).

Como al crear el PreparedStatement habíamos puesto la opción Statement.RETURN_GENERATED_KEYS, podemos ahora consultar las claves que se han generado en los insert. Para ello llamamos a ps.getGeneratedKeys() que nos devolverá un ResultSet con dichas claves.


Enlaces[editar]