La mayoría de los controladores JDBC proporcionan mejoras de rendimiento al agrupar varias llamadas a la misma declaración compilada. Al agrupar las actualizaciones en lotes, limita el número de viajes de ida y vuelta a la base de datos.

Operaciones por lotes básicas utilizando JdbcTemplate

Procesa por lotes JdbcTemplate implementando dos métodos de la interfaz especial BatchPreparedStatementSetter y pasando esta implementación como segundo parámetro en la llamada al método batchUpdate. El método getBatchSize se puede utilizar para establecer los valores de los parámetros de la declaración compilada. El método setValues se puede utilizar para establecer los valores de los parámetros de una declaración preparada. Este método se llama el número de veces especificado en la llamada getBatchSize. El siguiente ejemplo actualiza la tabla t_actor en función de las entradas de una lista, utilizando la lista completa como lote:

Java

public class JdbcActorDao implements ActorDao {
    private JdbcTemplate jdbcTemplate;
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    public int[] batchUpdate(final List<Actor> actors) {
        return this.jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                new BatchPreparedStatementSetter() {
                    public void setValues(PreparedStatement ps, int i) throws SQLException {
                        Actor actor = actors.get(i);
                        ps.setString(1, actor.getFirstName());
                        ps.setString(2, actor.getLastName());
                        ps.setLong(3, actor.getId().longValue());
                    }
                    public int getBatchSize() {
                        return actors.size();
                    }
                });
    }
    // ... métodos adicionales
}
Kotlin

class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private val jdbcTemplate = JdbcTemplate(dataSource)
    fun batchUpdate(actors: List<Actor>): IntArray {
        return jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                object: BatchPreparedStatementSetter {
                    override fun setValues(ps: PreparedStatement, i: Int) {
                        ps.setString(1, actors[i].firstName)
                        ps.setString(2, actors[i].lastName)
                        ps.setLong(3, actors[i].id)
                    }
                    override fun getBatchSize() = actors.size
                })
    }
    // ... métodos adicionales
}

Si está manejando una secuencia de actualización o leyendo desde un archivo, es posible que ya tenga un tamaño de lote preferido, pero es posible que el último lote no tenga tantos registros. En este caso, puede utilizar la interfaz InterruptibleBatchPreparedStatementSetter, que le permite interrumpir el lote después de que se agote la fuente de datos de entrada. El método isBatchExhausted le permite señalar el final de un lote.

Operaciones por lotes utilizando una lista de objetos

Plantilla JdbcTemplate y NamedParameterJdbcTemplate proporcionan una forma alternativa de proporcionar actualizaciones por lotes. En lugar de implementar una interfaz por lotes especializada, proporciona todos los valores de los parámetros en la llamada como una lista. El marco itera a través de estos valores y utiliza el definidor interno de las declaraciones compiladas. La API difiere dependiendo de si utiliza parámetros con nombre. Para los parámetros con nombre, especifica una matriz de SqlParameterSource, una entrada para cada miembro del grupo. Puede utilizar los métodos auxiliares SqlParameterSourceUtils.createBatch para crear esta matriz pasando una matriz de objetos basados en beans (usando captadores correspondientes a los parámetros), instancias Map con String-key (que contiene los parámetros correspondientes como valores), o una combinación de ambos.

El siguiente ejemplo muestra una actualización por lotes utilizando parámetros con nombre:

Java
        
public class JdbcActorDao implements ActorDao {
    private NamedParameterTemplate namedParameterJdbcTemplate;
    public void setDataSource(DataSource dataSource) {
        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    }
    public int[] batchUpdate(List<Actor>actors) {
        return this.namedParameterJdbcTemplate.batchUpdate(
                "update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
                SqlParameterSourceUtils.createBatch(actors));
    }
    // ... métodos adicionales
}
Kotlin

class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
    fun batchUpdate(actors: List<Actor>): IntArray {
        return this.namedParameterJdbcTemplate.batchUpdate(
                "update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
                SqlParameterSourceUtils.createBatch(actors));
    }
        // ... métodos adicionales
}

Para una declaración SQL que utiliza marcadores de posición clásicos ?, se pasa una lista que contiene un matriz de objetos con valores de actualización. Esta matriz de objetos debe contener un elemento para cada marcador de posición en la declaración SQL y deben estar en el mismo orden en que se definen en la declaración SQL.

El siguiente ejemplo es similar al anterior. , excepto que utiliza marcadores de posición JDBC clásicos ?:

Java

public class JdbcActorDao implements ActorDao {
    private JdbcTemplate jdbcTemplate;
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    public int[] batchUpdate(final List<<Actor> actors) {
        List<Object[]> batch = new ArrayList<Object[]>();
        for (Actor actor : actors) {
            Object[] values = new Object[] {
                    actor.getFirstName(), actor.getLastName(), actor.getId()};
            batch.add(values);
        }
        return this.jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                batch);
    }
    // ... métodos adicionales
}
Kotlin

class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private val jdbcTemplate = JdbcTemplate(dataSource)
    fun batchUpdate(actors: List<Actor>): IntArray {
        val batch = mutableListOf<Array<Any>>()
        for (actor in actors) {
            batch.add(arrayOf(actor.firstName, actor.lastName, actor.id))
        }
        return jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?", batch)
    }
    // ... métodos adicionales
}        

Todos los métodos de actualización por lotes que describimos anteriormente devuelven una matriz int que contiene el número de filas afectadas para cada entrada del lote. Este contador lo informa el controlador JDBC. Si el contador no está disponible, el controlador JDBC devuelve el valor -2.

En tal escenario , al configurar automáticamente los valores subyacentes PreparedStatement, el tipo JDBC correspondiente para cada valor debe derivarse del tipo Java dado. Si bien esto generalmente funciona bien, existe la posibilidad de que surjan problemas (por ejemplo, con los valores null contenidos en el mapa). En tal caso, Spring llama a ParameterMetaData.getParameterType de forma predeterminada, lo que puede resultar costoso cuando se utiliza el controlador JDBC. Debería utilizar el controlador más reciente y considerar establecer la propiedad spring.jdbc.getParameterType.ignore en true (como una propiedad del sistema JVM o mediante el SpringProperties ) si está experimentando un problema de rendimiento (como se informa en Oracle 12c, JBoss y PostgreSQL).

Además, se podría considerar especificar explícitamente los tipos JDBC apropiados a través de un BatchPreparedStatementSetter (como se mostró anteriormente) o mediante una matriz explícita de tipos pasados a la llamada basada en List<Object[]>, o mediante una llamada a registerSqlType en una instancia personalizada de MapSqlParameterSource, o mediante BeanPropertySqlParameterSource, que infiere el tipo de SQL a partir de el tipo de propiedad declarado en Java, incluso para un valor vacío.

Operaciones por lotes usando múltiples paquetes

El ejemplo anterior de actualización por lotes trató con paquetes que son tan grandes que necesitas dividirlos en varios paquetes más pequeños. Puede hacer esto usando los métodos mencionados anteriormente haciendo varias llamadas al método batchUpdate, pero ahora hay un método más conveniente. Este método acepta, además de la declaración SQL, una Collection de objetos que contienen parámetros, el número de actualizaciones para cada paquete y un ParameterizedPreparedStatementSetter para establecer los valores del parámetros de declaración compilados. El marco itera a través de los valores proporcionados y divide las llamadas de actualización en lotes del tamaño especificado.

El siguiente ejemplo muestra una actualización por lotes que utiliza un tamaño de lote de 100:

Java
 
public class JdbcActorDao implements ActorDao {
    private JdbcTemplate jdbcTemplate;
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    public int[][] batchUpdate(final Collection<Actor> actors) {
        int[][] updateCounts = jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                actors,
                100,
                (PreparedStatement ps, Actor actor) -> {
                    ps.setString(1, actor.getFirstName());
                    ps.setString(2, actor.getLastName());
                    ps.setLong(3, actor.getId().longValue());
                });
        return updateCounts;
    }
    // ... métodos adicionales
}
Kotlin

class JdbcActorDao(dataSource: DataSource) : ActorDao {
    private val jdbcTemplate = JdbcTemplate(dataSource)
    fun batchUpdate(actors: List<Actor>): Array<IntArray> {
        return jdbcTemplate.batchUpdate(
                    "update t_actor set first_name = ?, last_name = ? where id = ?",
                    actors, 100) { ps, argument ->
            ps.setString(1, argument.firstName)
            ps.setString(2, argument.lastName)
            ps.setLong(3, argument.id)
        }
    }
    // ... métodos adicionales
}

El método de actualización por lotes para esta llamada devuelve una matriz de matrices int que contiene una entrada de matriz para cada lote con una matriz del número de filas afectadas por cada actualización. La longitud de la matriz de nivel superior indica la cantidad de paquetes en ejecución y la longitud de la matriz de segundo nivel indica la cantidad de actualizaciones en ese paquete. La cantidad de actualizaciones en cada paquete debe coincidir con el tamaño de paquete especificado para todos los paquetes (excepto el último, que puede ser más pequeño), dependiendo de la cantidad total de objetos actualizables especificados. El contador de actualizaciones para cada declaración de actualización es el contador informado por el controlador JDBC. Si el contador no está disponible, el controlador JDBC devuelve -2.