JavaRush /Курсы /Модуль 4. Работа с БД /Транзакции при работе с базой данных

Транзакции при работе с базой данных

Модуль 4. Работа с БД
8 уровень , 0 лекция
Открыта

Зачем нужны транзакции

Очень часто при работе с базой данных возникает ситуация, когда нужно выполнить много разных действий, но смысл они имеют только вместе.

Например, мы пишем банковское ПО, которое должно сделать три вещи:

  • Списать деньги со счета клиента
  • Добавить деньги на счет получателя
  • Записать данные о проводке в “журнал проводок”

Если во время выполнения любого из этих действий возникнет ошибка, то остальные два нужно тоже отменять. Нельзя же списать деньги у клиента и не добавить их получателю? Ну или добавить получателю, но не списать у клиента?

Так вот, такая логическая группировка разных действий в одно называется транзакцией. Другими словами, транзакция — это группа действий, которые должны быть выполнены только все вместе. Если какое-либо действие не выполнилось или выполнилось с ошибкой, то все остальные действия должны быть отменены.

У транзакции обычно есть три состояния:

  • initial state — состояние системы перед выполнением группы действий
  • success state — состояние после выполнения группы действий
  • failed state — что-то пошло не так

При этом обычно есть три команды:

  • begin/start — выполняется перед началом логической группы действий
  • commit — выполняется после группы действий транзакции
  • rollback — запускает процесс возврата системы из failed state в initial state

Работает это так.

Сначала нужно открыть транзакцию — вызвать метод begin() или start(). Вызов этого метода обозначает состояние системы, к которому мы попробуем вернуться, если что-то пойдет не так.

Затем выполняются все действия, которые объединены в логическую группу — транзакцию.

Затем вызывается метод commit(). Его вызов обозначает конец логической группы действий, а также обычно запускает процесс реализации этих действий на практике.

Вспомни, как мы писали что-то в FileWriter: сначала все, что мы написали, сохраняется в памяти, а затем при вызове метода flush() все данные из буфера в памяти пишутся на диск. Вот этот flush() — это и есть коммит транзакции.

Ну а если во время работы транзакции произошла ошибка, то нужно инициировать процесс возврата к стартовому состоянию. Этот процесс называется rollback(), и за него обычно отвечает одноименный метод.

Грубо говоря, есть 2 способа завершить транзакцию:

  • COMMIT — подтверждаем все внесенные изменения
  • ROLLBACK — откатываем все внесенные изменения

Транзакции в JDBC

Практически каждая СУБД умеет работать с транзакциями. Так что и у JDBC поддержка этого дела тоже есть. Реализовано все очень просто.

Во-первых, каждый вызов метода execute() объекта Statement выполняется в отдельной транзакции. Для этого у Connection есть параметр AutoCommit. Если он выставлен в true, то commit() будет вызываться после каждого вызова метода execute().

Во-вторых, если ты хочешь выполнить несколько команд в одной транзакции, то сделать это можно так:

  • отключаем AutoCommit
  • вызываем наши команды
  • вызываем метод commit() явно

Выглядит это очень просто:


  connection.setAutoCommit(false);
 
  Statement statement = connection.createStatement();
  int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  int rowsCount3 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
 
  connection.commit();

Если во время работы метод commit() на сервере произойдет ошибка, то SQL-сервер отменит все три действия.

Но бывают ситуации, когда ошибка возникает еще на стороне клиента, и мы так и не дошли до вызова метода commit():


  connection.setAutoCommit(false);
 
  Statement statement = connection.createStatement();
  int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  int rowsCount3 = statement.executeUpdate("UPDATE  несколько опечаток приведут к исключению");
 
  connection.commit();

Если во время работы одного executeUpdate() произойдет ошибка, то метод commit() вызван так и не будет. Чтобы откатить все сделанные действия, нужно вызвать метод rollback(). Обычно это выглядит так:


  try{
  	connection.setAutoCommit(false);
 
  	Statement statement = connection.createStatement();
  	int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount3 = statement.executeUpdate("UPDATE  несколько опечаток приведут к исключению");
 
	  connection.commit();
 }
 catch (Exception e) {
   connection.rollback();
}

Точки сохранения

С появлением JDBC 3.0 появилась возможность более эффективно работать с откатом транзакции. Теперь можно устанавливать точки сохранения — save points, а при вызове операции rollback() откатываться к конкретной точке сохранения.

Для того, чтобы сохраниться, нужно создать точку сохранения, делается это командой:


   Savepoint save = connection.setSavepoint();

Возврат к точке сохранения делается командой:


   connection.rollback(save);

Давай попробуем добавить точку сохранения перед нашей проблемной командой:


  try{
  	connection.setAutoCommit(false);
 
  	Statement statement = connection.createStatement();
  	int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
 
  	Savepoint save = connection.setSavepoint();
 	 try{
      	int rowsCount3 = statement.executeUpdate("UPDATE  несколько опечаток приведут к исключению");
 	 }
 	 catch (Exception e) {
    	   connection.rollback(save);
 	 }
 
	  connection.commit();
 }
 catch (Exception e) {
   connection.rollback();
}

Мы организовали как бы вложенные транзакции, добавив save-point перед вызовом проблемного метода, и возврат к сохраненному состоянию с помощью вызова метода rollback(save).

Да, это очень похоже на save/load в играх.

1
Задача
Модуль 4. Работа с БД, 8 уровень, 0 лекция
Недоступна
Работа с транзакциями
В методе main создай подключение к БД с помощью метода getConnection(String, String, String) класса DriverManager. Используй URL "jdbc:mysql://localhost:3306/test", пользователя "root" и такой же пароль. Отключи AutoCommit.
1
Задача
Модуль 4. Работа с БД, 8 уровень, 0 лекция
Недоступна
Откат транзакции
В методе main создай подключение к БД с помощью метода getConnection(String, String, String) класса DriverManager. Используй URL "jdbc:mysql://localhost:3306/test", пользователя "root" и такой же пароль. Отключи AutoCommit.
Комментарии (9)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
15 апреля 2025
Я правильно понимаю, что если произошёл откат к сейвпоинту, то нерабочий запрос просто пропускается, а остальные срабатывают? Например, в моём коде первые два запроса применяются а нерабочий третий запрос просто пропускается.
Zim4ik Уровень 51
15 октября 2025
Да совершенно верно, Ты откатил только к savepoint, а не ко всей транзакции.После этого ты вызвал connection.commit() — и все изменения до savepoint были зафиксированы.
Вадим Уровень 106
8 декабря 2024
Была ошибка, если использовать SQLExeption вместо просто Exeption
Андрей Уровень 109
28 июня 2024
Не понял этого: Если во время работы одного executeUpdate() произойдет ошибка, то метод commit() вызван так и не будет. Чтобы откатить все сделанные действия, нужно вызвать метод rollback(). Зачем нам откатывать изменения, если commit() вызван не будет? Изменения же и так не сохранятся без коммита. Типа чтобы транзакция не висела и не блокировала объекты?
Gans Electro Уровень 4
7 июля 2024
Да, чтобы транзакция не висела

//Уже отработали и внесли "локальные изменения"
int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");

//Ошибка
int rowsCount3 = statement.executeUpdate("UPDATE  несколько опечаток приведут к исключению");

//Локальные изменения откатились
connection.rollback();
Исправляем ошибку и все заново
Сергей Уровень 109
19 июня 2025
GPT: 👉 Потому что до ошибки могли уже быть внесены временные изменения, которые нужно отменить и освободить ресурсы. 🔥 Почему всё-таки нужно вызывать rollback(): Причина - Почему это важно ❗ Снять блокировки - Транзакция может блокировать строки или таблицы до rollback() 🧼 Очистить серверное состояние - Транзакция "висит", даже если клиент отвалился ⚠️ Освободить ресурсы (логи, буферы) - Память и логи транзакций не очистятся без commit() или rollback() 🧠 Явное завершение это хорошая практика - Код становится предсказуемым и безопасным Да, без commit() изменения не сохранятся. Но без rollback() они и не отменятся! Они просто будут висеть, блокируя БД.
Andrey Vysotsky Уровень 32
10 апреля 2024
что-то все коментаторы отвалились. Либо не у кого вопросов нет, либо все уже забили
И. Ж. Уровень 41
28 апреля 2024
Все всем ясно сходу, даже в теории здесь не могут возникнуть вопросы, элементарно..
Andrew Cooper Уровень 1 Expert
3 ноября 2023
а где про транзакции посредством самого запроса? 👿