Большинство драйверов JDBC обеспечивают повышение производительности при пакетном множественном обращении к одному и тому же скомпилированному стейтменту. Группируя обновления в пакеты, вы ограничиваете количество циклических обращений к базе данных.
Основные пакетные операции с использованием JdbcTemplate
Вы выполняете пакетную обработку JdbcTemplate
, реализуя два метода специального интерфейса BatchPreparedStatementSetter
и передавая эту реализацию в качестве второго параметра в вызове метода batchUpdate
. Метод getBatchSize
можно использовать для установки значений параметров скомпилированного стейтмента. Метод setValues
можно использовать для установки значений параметров подготовленной инструкции. Этот метод вызывается то количество раз, которое будет указано в вызове getBatchSize
. В следующем примере таблица t_actor
обновляется на основе записей в списке, причем в качестве пакета используется весь список:
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();
}
});
}
// ... дополнительные методы
}
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
})
}
// ... дополнительные методы
}
Если вы обрабатываете поток обновлений или чтение из файла, у вас уже может быть предпочтительный размер пакета, но в последнем пакете может не оказаться такого количества записей. В этом случае можно использовать интерфейс InterruptibleBatchPreparedStatementSetter
, который позволяет прервать пакет после исчерпания источника входных данных. Метод isBatchExhausted
позволяет сигнализировать о завершении пакета.
Пакетные операции с использованием списка объектов
Шаблон JdbcTemplate
и NamedParameterJdbcTemplate
предоставляют альтернативный способ обеспечения пакетного обновления. Вместо реализации специализированного пакетного интерфейса вы предоставляете все значения параметров в вызове в виде списка. Фреймворк перебирает эти значения и использует внутренний сеттер скомпилированных стейтментов. API-интерфейс различается в зависимости от того, используете ли вы именованные параметры. Для именованных параметров вы указываете массив SqlParameterSource
, по одной записи для каждого члена группы. Можно использовать вспомогательные методы SqlParameterSourceUtils.createBatch
для создания этого массива, передавая массив объектов на основе бина (с использованием геттеров, соответствующих параметрам), экземпляров Map
со String
-ключом (содержащим соответствующие параметры в качестве значений), или комбинации и того, и другого.
В следующем примере показано пакетное обновление с использованием именованных параметров:
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));
}
// ... дополнительные методы
}
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));
}
// ... дополнительные методы
}
Для SQL-стейтмента, использующего классические плейсхолдеры ?
, вы передаете список, содержащий массив объектов со значениями обновления. Этот массив объектов должен содержать по одному элементу для каждого плейсхолдера в SQL-стейтменте, и они должны располагаться в том же порядке, в котором они определены в SQL-стейтменте.
Следующий пример аналогичен предыдущему, за исключением того, что в нем используются классические для JDBC плейсхолдеры ?
:
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);
}
// ... дополнительные методы
}
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)
}
// ... дополнительные методы
}
Все методы пакетного обновления, которые мы описали ранее, возвращают массив int
, содержащий количество затронутых строк для каждой пакетной записи. Этот счетчик сообщается драйвером JDBC. Если счетчик недоступен, драйвер JDBC возвращает значение -2
.
В таком сценарии, при автоматической установке значений в базовом PreparedStatement
, соответствующий тип JDBC для каждого значения должен являться производным от данного типа Java. Хотя обычно это работает хорошо, существует вероятность возникновения проблем (например, с null
значениями, содержащимися в Map). В таком случае Spring по умолчанию вызывает ParameterMetaData.getParameterType
, что может быть затратным при использовании вашего драйвера JDBC. Следует использовать последнюю версию драйвера и рассмотреть возможность установки свойства spring.jdbc.getParameterType.ignore
в true
(как системное свойство JVM или через механизм SpringProperties
), если вы сталкиваетесь с проблемой с производительностью (как сообщается в Oracle 12c, JBoss и PostgreSQL).
Кроме того, можно рассмотреть вариант явного указания соответствующих типов JDBC либо через BatchPreparedStatementSetter
(как показано ранее), либо через явный массив типов, передаваемый вызову на основе List<Object[]>
, либо через вызов registerSqlType
для пользовательского экземпляра MapSqlParameterSource
, либо через BeanPropertySqlParameterSource
, который выводит тип SQL из объявленного в Java типа свойства даже для пустого значения.
Пакетные операции с использованием нескольких пакетов
В предыдущем примере пакетного обновления речь шла о пакетах, которые настолько объемны, что требуется разбить их на несколько меньших пакетов. Осуществить это можно с помощью методов, упомянутых ранее, выполнив несколько вызовов метода batchUpdate
, но теперь есть и более удобный метод. Этот метод принимает, помимо SQL-инструкции, Collection
объектов, содержащую параметры, количество обновлений для каждого пакета и ParameterizedPreparedStatementSetter
для установки значений параметров скомпилированного стейтмента. Фреймворк перебирает предоставленные значения и разбивает вызовы обновления на пакеты указанного размера.
В следующем примере показано пакетное обновление, в котором используется размер пакета 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;
}
// ... дополнительные методы
}
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)
}
}
// ... дополнительные методы
}
Метод пакетного обновления для этого вызова возвращает массив массивов int
, который содержит запись массива для каждого пакета с массивом количества затронутых строк для каждого обновления. Длина массива верхнего уровня указывает на количество запущенных пакетов, а длина массива второго уровня – на количество обновлений в этом пакете. Количество обновлений в каждом пакете должно соответствовать размеру пакета, указанному для всех пакетов (за исключением последнего, который может быть меньше), в зависимости от общего количества указанных обновляемых объектов . Счетчик обновлений для каждой инструкции обновления – это счетчик, сообщаемый драйвером JDBC. Если счетчик недоступен, драйвер JDBC возвращает значение -2
.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ