Gymterview
middle

Как управлять транзакциями в JDBC

Транзакция в JDBC — это набор операций с базой данных, выполняемых как единое целое: либо все операции завершаются успешно (commit), либо все отменяются (rollback).

По умолчанию JDBC работает в режиме autoCommit = true, то есть каждый SQL-запрос автоматически коммитится. Для ручного управления транзакциями необходимо отключить автокоммит.

Базовое управление

Пример
connection.setAutoCommit(false); // начинаем транзакцию
try {
    // выполняем SQL-операции
    connection.commit();         // подтверждаем
} catch (SQLException e) {
    connection.rollback();       // откатываем при ошибке
    throw e;
} finally {
    connection.setAutoCommit(true); // восстанавливаем режим
}

Savepoint

Savepoint позволяет создать промежуточную точку внутри транзакции, к которой можно откатиться без отката всей транзакции:

Пример
Savepoint sp = connection.setSavepoint("before_notification");
try {
    // дополнительная операция
} catch (SQLException e) {
    connection.rollback(sp); // откат только до savepoint
}
connection.commit(); // основная операция сохраняется

Сравнение с Spring @Transactional

Аспект Чистый JDBC Spring @Transactional
Управление соединением Вручную Автоматически
Начало транзакции setAutoCommit(false) Декларативно через аннотацию
Коммит connection.commit() Автоматически при успешном завершении
Откат connection.rollback() Автоматически при RuntimeException
Уровень изоляции setTransactionIsolation() @Transactional(isolation = …)
Propagation Нет поддержки Полная поддержка (REQUIRED, REQUIRES_NEW и т.д.)
Полный пример: перевод денег между счетами
public void transferMoney(Connection connection, long fromAccountId,
                          long toAccountId, BigDecimal amount)
        throws SQLException {
    connection.setAutoCommit(false);

    try {
        // Списание со счёта отправителя
        try (PreparedStatement debit = connection.prepareStatement(
                "UPDATE accounts SET balance = balance - ? "
                + "WHERE id = ? AND balance >= ?")) {
            debit.setBigDecimal(1, amount);
            debit.setLong(2, fromAccountId);
            debit.setBigDecimal(3, amount);
            int updated = debit.executeUpdate();
            if (updated == 0) {
                throw new SQLException(
                    "Недостаточно средств на счёте " + fromAccountId);
            }
        }

        // Зачисление на счёт получателя
        try (PreparedStatement credit = connection.prepareStatement(
                "UPDATE accounts SET balance = balance + ? WHERE id = ?")) {
            credit.setBigDecimal(1, amount);
            credit.setLong(2, toAccountId);
            credit.executeUpdate();
        }

        connection.commit();
    } catch (SQLException e) {
        connection.rollback();
        throw e;
    } finally {
        connection.setAutoCommit(true);
    }
}
Вспомогательный класс TransactionTemplate
public class TransactionTemplate {

    private final DataSource dataSource;

    public TransactionTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @FunctionalInterface
    public interface TransactionCallback<T> {
        T execute(Connection connection) throws SQLException;
    }

    public <T> T executeInTransaction(TransactionCallback<T> callback) {
        try (Connection conn = dataSource.getConnection()) {
            conn.setAutoCommit(false);
            try {
                T result = callback.execute(conn);
                conn.commit();
                return result;
            } catch (Exception e) {
                conn.rollback();
                throw new RuntimeException("Ошибка в транзакции", e);
            }
        } catch (SQLException e) {
            throw new RuntimeException("Ошибка подключения", e);
        }
    }
}

Важное

  • Всегда отключайте autoCommit перед началом транзакции
  • Используйте try-catch-finally для гарантированного rollback при ошибке
  • Закрывайте Connection после завершения работы с транзакцией
  • Уровень изоляции устанавливается через setTransactionIsolation() — выбирайте минимально необходимый

Частые ошибки

  • Забывать вызвать rollback() в блоке catch — данные могут остаться в неконсистентном состоянии
  • Не восстанавливать autoCommit после транзакции — последующие операции не будут коммититься
  • Держать транзакцию открытой слишком долго — блокирует записи в БД
  • Не закрывать PreparedStatement внутри транзакции — утечка ресурсов

Как используется в 2026

  • В большинстве проектов транзакции управляются через Spring @Transactional
  • Чистый JDBC для транзакций используется в микрофреймворках (Javalin, Spark) и библиотеках
  • Saga-паттерн для распределённых транзакций между микросервисами вместо XA
  • Virtual threads (Java 21+) упрощают написание транзакционного кода без callback-ов

На собеседовании: покажите паттерн setAutoCommit(false) -> try { … commit() } catch { rollback() }. Упомяните Savepoint для частичного отката. Обязательно скажите, что в реальных проектах используется Spring @Transactional, а чистый JDBC — для понимания того, что происходит «под капотом». Частый follow-up: что такое propagation и почему его нет в чистом JDBC.