Как управлять транзакциями в 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.