Gymterview
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 с количеством затронутых строк для каждой команды.