middle
Как выполнять batch-операции в JDBC
Batch-операции (пакетные операции) — это механизм отправки нескольких SQL-команд на сервер за один сетевой вызов, значительно сокращающий количество обращений к СУБД и повышающий производительность.
Вместо отправки каждого SQL-запроса отдельно, batch-операции группируют запросы и отправляют их одним пакетом:
addBatch()— добавляет SQL-команду в пакетexecuteBatch()— отправляет все накопленные команды на серверclearBatch()— очищает накопленный пакет
Сравнение производительности (вставка 10 000 записей)
| Подход | Время | Сетевых обращений |
|---|---|---|
| Одиночный INSERT | ~10 000 мс | 10 000 |
| Batch (размер 1 000) | ~500 мс | 10 |
| Batch + rewriteBatchedStatements | ~200 мс | 10 |
Batch с Statement
public void batchWithStatement(Connection connection) throws SQLException {
try (Statement stmt = connection.createStatement()) {
connection.setAutoCommit(false);
stmt.addBatch("INSERT INTO products (name, price) VALUES ('Телефон', 29999)");
stmt.addBatch("INSERT INTO products (name, price) VALUES ('Ноутбук', 74999)");
stmt.addBatch("INSERT INTO products (name, price) VALUES ('Планшет', 19999)");
stmt.addBatch("UPDATE products SET price = price * 0.9 WHERE price > 50000");
int[] results = stmt.executeBatch();
connection.commit();
for (int i = 0; i < results.length; i++) {
System.out.println("Команда " + i + ": затронуто строк = " + results[i]);
}
} catch (BatchUpdateException e) {
connection.rollback();
int[] updateCounts = e.getUpdateCounts();
for (int i = 0; i < updateCounts.length; i++) {
if (updateCounts[i] == Statement.EXECUTE_FAILED) {
System.err.println("Команда " + i + " завершилась с ошибкой");
}
}
}
}
Batch с PreparedStatement (рекомендуемый подход)
public void batchInsertUsers(Connection connection, List<User> users)
throws SQLException {
String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
connection.setAutoCommit(false);
int batchSize = 1000;
int count = 0;
for (User user : users) {
ps.setString(1, user.getName());
ps.setString(2, user.getEmail());
ps.setInt(3, user.getAge());
ps.addBatch();
count++;
// Выполняем промежуточный batch каждые 1000 записей
if (count % batchSize == 0) {
ps.executeBatch();
ps.clearBatch();
}
}
// Выполняем оставшиеся записи
ps.executeBatch();
connection.commit();
} catch (BatchUpdateException e) {
connection.rollback();
throw new RuntimeException("Ошибка batch-вставки", e);
}
}
Batch-операции с Spring JdbcTemplate
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
public class UserRepository {
private final JdbcTemplate jdbcTemplate;
public UserRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void batchInsert(List<User> users) {
String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i)
throws SQLException {
User user = users.get(i);
ps.setString(1, user.getName());
ps.setString(2, user.getEmail());
ps.setInt(3, user.getAge());
}
@Override
public int getBatchSize() {
return users.size();
}
});
}
}
Важное
- Batch-операции эффективны при вставке/обновлении от 100+ записей
- Всегда оборачивайте batch в транзакцию (setAutoCommit(false) + commit())
- Для больших объёмов разбивайте batch на части (обычно по 1000-5000 записей)
- PreparedStatement batch безопаснее — защита от SQL Injection
Частые ошибки
- Не отключать autoCommit — каждая команда коммитится отдельно, теряя смысл пакетирования
- Накапливать слишком большой batch без промежуточного executeBatch() — может привести к OutOfMemoryError
- Не обрабатывать BatchUpdateException — невозможно определить, какие записи вставлены успешно
- Забывать clearBatch() после executeBatch() при обработке данных частями
Как используется в 2026
- Spring Data JDBC поддерживает batch-вставки через saveAll() с настройкой spring.jdbc.template.batch-size
- jOOQ предлагает типобезопасные batch-операции с автоматической оптимизацией
- Для массовой загрузки всё чаще используется COPY (PostgreSQL) или LOAD DATA INFILE (MySQL)
- В реактивном стеке R2DBC поддерживает batch через Statement.add()
На собеседовании: объясните зачем нужен batch (сокращение сетевых вызовов), назовите три метода (addBatch, executeBatch, clearBatch) и скажите про разбиение на части при большом объёме данных. Частый follow-up: что вернёт executeBatch() — массив int с количеством затронутых строк для каждой команды.