Использование batch режима выполнения запросов обычно гораздо эффективнее. В этом режиме запросы не выполняются сразу, каждый по отдельности, а накапливаются и выполняются одним большим запросом с множеством данных. Например, если вы делаете несколько update запросов, то каждый запрос будет выполнен как отдельный самостоятельный запрос. В batch режиме ваши update запросы будут отправлены одним заданием, что производительнее.
В этом примере используется структура данных и код из статьи «запросы в Spring JDBC».
Batch операции над списком объектов.
Самое приятное улучшение, которое вносит Spring JDBC в обычные batch запросы, это автоматическое создание batch’а из массива объектов и запроса. Если используется NamedParameterJdbcTemplate, вы можете написать запрос имена параметров которого совпадают с именами полей класса. Затем из массива объектов необходимо получить массив sql параметров и передать его вместе с запросом в метод batchUpdate(). Всё остальное Spring делает сам.
1
2
3
4
5
6
7
8
9
|
private static final String CREATE_QUERY
= "INSERT INTO CUSTOMERS (ID,EMAIL) VALUES(:id, :email)";
@Override
public final void add(final List<Customer> customers) {
SqlParameterSource[] batch = SqlParameterSourceUtils
.createBatch(customers.toArray());
jdbcTemplate.batchUpdate(CREATE_QUERY, batch);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Test
public void testAdd() {
Customer c = new Customer();
c.setId(101);
testedObject.add(Collections.singletonList(c));
assertTrue(testedObject.all()
.stream()
.findFirst()
.isPresent());
}
|
Ручное задание параметров batch запросов
При всём удобстве, у подхода описанного выше есть пара недостатков: во-первых он работает только с NamedParameterJdbcTemplate, во-вторых требует наличия класса с именно такими полями, как указаны в запросе. Если какое-либо из требований удовлетворить нельзя, приходится использовать другие инструменты. Стандартным решением является реализация callback интерфейса BatchPreparedStatementSetter, который и будет наполнять данными каждый конкретный элемент batch’а.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private static class SkuAddBatch implements BatchPreparedStatementSetter {
private final List<Sku> skus;
public SkuAddBatch(final List<Sku> s) {
this.skus = s;
}
@Override
public final void setValues(
final PreparedStatement ps,
final int i) throws SQLException {
ps.setInt(1, skus.get(i).getId());
ps.setString(2, skus.get(i).getDescription());
}
@Override
public int getBatchSize() {
return skus.size();
}
}
|
1
2
3
4
|
@Override
public final void add(final List<Sku> skus) {
jdbcTemplate.batchUpdate(ADD_QUERY, new SkuAddBatch(skus));
}
|
1
2
3
4
5
6
7
8
9
10
|
@Test
public void testCreate() {
Sku expected = new Sku();
expected.setId(500);
expected.setDescription("NEWBIE");
testedObject.add(Collections.singletonList(expected));
assertThat(testedObject.getDescription(500), is("NEWBIE"));
}
|
В BatchPreparedStatementCreator необходимо реализовать два метода. getBatchSize() сообщает Spring JDBC какой размер данных ожидать, setValues() вызывается столько раз, сколько вернул getBatchSize() и получает в качесте аргументов PreparedStatement и номер текущего элемента batch’а. setValues() должен соответствующим образом выставить параметры PreparedStatement.
Разделение batch’ей.
В некоторых случаях размер данных для одного batch может быть насколько большой, что его было бы удобнее разделить на несколько подбатчей. Это можно сделать вручную, например разбив коллекцию на несколько частей или лучше поручить Spring JDBC. Для использования subbatch необходимо реализовать (да да, ещё один) интефейс ParameterizedPreparedStatementSetter. В отличие от BatchPreparedStatementSetter он типизируется конкретным объектом и самостоятельно не управляет размером batch’а.
1
2
3
4
5
6
7
8
9
10
11
|
private static class OrderSetter
implements ParameterizedPreparedStatementSetter<Order> {
@Override
public final void setValues(
final PreparedStatement ps,
final Order o) throws SQLException {
ps.setInt(1, o.getId());
ps.setInt(2, o.getCustomer().getId());
}
}
|
1
2
3
4
5
6
7
8
|
@Override
public final void add(final Collection<Order> orders) {
jdbcTemplate.batchUpdate(
ADD_QUERY,
orders,
BATCH_SIZE,
new OrderSetter());
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Test
public void testGet() {
Collection<Order> actual = new ArrayList<>();
for (Customer c : customerRepository.all()) {
for (int i=0; i<50; i++) {
Order o = new Order();
o.setId(c.getId()*1000+i);
o.setCustomer(c);
actual.add(o);
}
}
testedObject.add(actual);
assertThat(testedObject.get(100000).getCustomer().getId(), is(100));
}
|
Реализация ParameterizedPreparedStatementSetter передаётся методу batchUpdate() вмест с размером реального batch, который будет передан в базу, коллекцией объектов с данными и запросом.
Код примера доступен на github.