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:
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
}
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:
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
}
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
?
:
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
}
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:
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
}
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
.
GO TO FULL VERSION