Java JDBC-транзакции
Управляйте транзакциями в Java JDBC с помощью setAutoCommit, commit, rollback и savepoints.
Транзакция объединяет несколько SQL-операторов в одну единицу по принципу «всё или ничего»: либо все изменения фиксируются, либо не фиксируется ни одно. Классический пример — банковский перевод: списание с одного счёта и зачисление на другой. Выполнение одной операции без другой привело бы к потере или появлению денег из ниоткуда. JDBC управляет транзакциями через объект Connection.
В этой главе рассматривается, как открыть транзакцию, отключив автофиксацию, как работают commit() и rollback(), как savepoints позволяют отменить часть транзакции, а также как уровни изоляции соотносят согласованность с параллелизмом.
Автофиксация включена по умолчанию
Новое соединение работает в режиме автофиксации (auto-commit): каждый оператор фиксируется сразу после выполнения. Это нормально для отдельных операторов, но неприемлемо для многооператорных единиц работы. Чтобы открыть транзакцию, нужно отключить автофиксацию:
conn.setAutoCommit(false); // begin a transaction
try {
// ... several statements ...
conn.commit(); // make all changes permanent
} catch (SQLException e) {
conn.rollback(); // undo everything since the last commit
throw e;
} finally {
conn.setAutoCommit(true); // restore default for the pool
}commit и rollback
commit() делает все изменения с момента начала транзакции постоянными и видимыми для других. rollback() отменяет их все. Золотое правило: фиксируйте при успехе, откатывайте при любом исключении. Если забыть вызвать rollback, соединение продолжит удерживать блокировки и незавершённую транзакцию.
Пример переноса средств
Перевод использует два объекта PreparedStatement, выполняет оба UPDATE и фиксирует транзакцию только после выполнения обоих. Если один из них выбросит исключение, блок catch откатит всё целиком, чтобы деньги не исчезли и не появились из ниоткуда:
conn.setAutoCommit(false);
try (PreparedStatement debit = conn.prepareStatement(
"UPDATE acct SET bal = bal - ? WHERE id = ?");
PreparedStatement credit = conn.prepareStatement(
"UPDATE acct SET bal = bal + ? WHERE id = ?")) {
debit.setBigDecimal(1, amount); debit.setInt(2, fromId); debit.executeUpdate();
credit.setBigDecimal(1, amount); credit.setInt(2, toId); credit.executeUpdate();
conn.commit();
} catch (SQLException e) {
conn.rollback();
throw e;
}Savepoints: частичный откат
Savepoint — это маркер внутри транзакции, до которого можно выполнить откат, отменив более поздние действия, но сохранив более ранние. Это полезно, когда один необязательный шаг внутри большой единицы работы может завершиться неудачей, но вы не хотите отменять всю транзакцию:
Savepoint sp = conn.setSavepoint("afterDebit");
// ... risky step ...
conn.rollback(sp); // undo back to the savepoint, not the whole transactionДва правила, которые нужно помнить: откат до savepoint не завершает транзакцию — после него всё равно нужен commit() или полный rollback() — и savepoints, которые больше не нужны, следует освобождать с помощью conn.releaseSavepoint(sp), чтобы освободить ресурсы базы данных. Savepoints существуют только при отключённой автофиксации.
Уровни изоляции
setTransactionIsolation(...) позволяет обменять согласованность на параллелизм. От слабейшего к сильнейшему: READ_UNCOMMITTED (видит незафиксированные «грязные» строки других транзакций), READ_COMMITTED (стандартный по умолчанию), REPEATABLE_READ (повторное чтение возвращает те же строки) и SERIALIZABLE (транзакции выполняются как будто строго последовательно). Более сильные уровни предотвращают больше аномалий, но допускают меньший параллелизм.
Аномалии, от которых защищает каждый уровень, по порядку:
- Грязное чтение — чтение строки, изменённой другой транзакцией, но ещё не зафиксированной. Предотвращается начиная с
READ_COMMITTED. - Неповторяемое чтение — повторное чтение той же строки возвращает другое значение, потому что другая транзакция зафиксировала изменение в промежутке. Предотвращается начиная с
REPEATABLE_READ. - Фантомное чтение — повторный запрос возвращает дополнительные строки, потому что другая транзакция вставила подходящие строки. Предотвращается только на уровне
SERIALIZABLE.
Устанавливайте уровень до начала работы и проверяйте, что реально поддерживает ваша база данных, с помощью DatabaseMetaData.supportsTransactionIsolationLevel(...) — не каждый драйвер поддерживает все уровни.
Рабочий пример: уровни изоляции и единица работы
Эта программа выводит четыре константы уровня изоляции в порядке возрастания силы, а затем моделирует перевод как атомарную единицу — подготавливает два обновления и либо фиксирует оба, либо откатывает оба — воспроизводя цикл setAutoCommit/commit/rollback без реальной базы данных.
Что можно извлечь из запуска:
- Константы изоляции возрастают по силе:
READ_UNCOMMITTED(1),READ_COMMITTED(2),REPEATABLE_READ(4),SERIALIZABLE(8). Одну из них передают вsetTransactionIsolation, чтобы выбрать, какой уровень аномалий параллелизма допустим. - Перевод подготавливает оба обновления перед фиксацией. Это суть транзакции: два
UPDATE— одна единица, иcommit()происходит только после того, как оба готовы. - Шаг
commit()здесь перемещает оба оператора изpendingвcommittedодновременно — моделируя, как база данных делает все изменения постоянными и видимыми в один момент, никогда наполовину. - Блок
catchочищаетpending— аналогrollback(). Вывод: дисциплина такова: любое исключение внутри транзакции должно приводить к откату, иначе база данных останется в частично изменённом состоянии. transfer applied: trueсвидетельствует об успешной фиксации. Если изменить логику так, чтобы условие не выполнялось, произойдёт откат без применения каких-либо изменений — именно это гарантирует принцип «всё или ничего», ради которого существуют транзакции.
Практика
Связанные темы
- JDBC Connection — объект, предоставляющий auto-commit, commit и rollback.
- PreparedStatement — безопасный способ выполнять параметризованные обновления внутри транзакции.
- Batch Processing — группировка множества операторов; батчи и транзакции часто используются вместе.
- DatabaseMetaData — запрос о том, какие уровни изоляции поддерживает ваш драйвер.