Транзакции в java sql

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

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

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

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

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

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

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

  • 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() .

Читайте также:  Обо мне

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

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

 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() . Обычно это выглядит так:

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

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

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

 Savepoint save = connection.setSavepoint();

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

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

 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)

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

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

Источник

Управление транзакциями, commit rollback

Транзакция Transaction включает одно или несколько изменений в базе данных, которые после выполнения либо все фиксируются (commit), либо все откатываются назад (rollback). При вызове метода commit или rollback текущая транзакция заканчивается и начинается другая.

По умолчанию каждое новое соединение находится в режиме автофиксации (autocommit = true). Это означает автоматическую фиксацию (commit) транзакции после выполнения каждого запроса. В этом случае транзакция включает только одно изменение (один запрос).

Если autocommit запрещен, т.е. равен false, то транзакция не заканчивается до явного вызова commit или rollback, включая, таким образом, все выражения, выполненные с момента последнего вызова commit или rollback. В этом случае все SQL-запросы в транзакции фиксируются или откатываются группой.

Читайте также:  Python текущая директория файла

Метод фиксации commit завершает все изменения в БД, проделанные SQL-выражением, и снимает также все блокировки, установленные транзакцией. Метод rollback наоборот — не сохранит изменения и восстановит исходное состояние на момент начала транзакции.

Иногда пользователю нужно, чтобы какое-либо изменение не вступило в силу до тех пор, пока не вступит в силу предыдущее изменение. Этого можно достичь запрещением autocommit и группировкой обоих запросов в одну транзакцию. Если оба изменения произошли успешно, то вызывается метод commit, который переносит эффект от этих изменений в БД; если одно или оба запроса не прошли, то вызывается метод rollback, который возвращает прежнее состояние БД.

Большинство JDBC-драйверов поддерживают транзакции. В действительности драйвер, соответствующий спецификации JDBC, обязан поддерживать их. Интерфейс DatabaseMetaData позволяет получить информацию об уровнях изолированности транзакций, которые поддерживаются данной СУБД.

Пример использования транзакции — commit, autocommit

Connection connection = . ; // Сброс автофиксации connection.setAutoCommit(false); // Первая транзакция PreparedStatement updateSales = connection.prepareStatement( "UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ?"); updateSales.setInt(1, 50); updateSales.setString(2, "Colombian"); updateSales.executeUpdate(); // Вторая транзакция PreparedStatement updateTotal = connection.prepareStatement( "UPDATE COFFEES SET TOTAL = TOTAL + ? WHERE COF_NAME LIKE ?"); updateTotal.setInt(1, 50); updateTotal.setString(2, "Colombian"); updateTotal.executeUpdate(); // Завершение транзакции connection.commit(); // Восстановление по умолчанию connection.setAutoCommit(true);

В примере для соединения Connection режим автофиксации отключен и два оператора updateSales и updateTotal будут зафиксированы вместе при вызове метода commit.

В последней строке примера режим автофиксации autocommit восстанавливается. То есть, каждый следующий запрос опять будет фиксироваться автоматически после своего завершения.

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

Уровни изолированности транзакций, dirty read

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

Допустим, что один из пользователей обновляет параметры заказчика (адрес, телефон, email) и программа требует подтверждения выполнения транзакции. В это же время другой пользователь читает информацию из базы данных о данном заказчике. Прочитает ли второй пользователь новые и не подтвержденные данные, или будет читать старые? Ответ зависит от уровня изоляции транзакции. Если транзакция разрешает другим программам читать не подтвержденные данные, то другая программа не будет ожидать окончания транзакции. Но здесь возникает компромисс — если транзакция будет отменена, то вторая другая программа может прочитать ошибочные данные.

Есть несколько способов разрешения конфликтов между одновременно выполняющимися транзакциями. Разработчик может определить уровень изолированности так, что пока одна транзакция изменяет какое-либо значение, вторая транзакция могла бы прочитать обновленное значение до того, пока первая не выполнит commit или rollback. Для этого следует установить уровень изолированности TRANSACTION_READ_UNCOMMITTED:

Connection connection; . connection.setTransactionIsolation(TRANSACTION_READ_UNCOMMITTED);

В данном коде серверу указано на возможность чтения измененных значений до того, как выполнится commit, т.е. определена возможность «грязного чтения» («dirty read«).

Читайте также:  Opening xml file in php

По умолчанию уровень изоляции транзакций обычно установлен в READ_COMMITED.

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

В связи с тем, что уровни изоляции, предлагаемые различными поставщиками СУБД, могут меняться, Вам следует обратиться к документации за дополнительной информацией. Уровни изоляции не стандартизованы для платформы J2EE.

Чем выше уровень изолированности транзакций, тем больше внимания СУБД уделяет устранению конфликтов. Интерфейс Connection определяет пять таких уровней. Минимальный из них соответствует случаю, когда транзакции не поддерживаются вовсе, а максимальный — невозможности существования более одной транзакции в любой момент времени.

Обычно, чем выше уровень изолированности, тем медленнее выполняется приложение (из-за избыточной блокировки). При выборе конкретного уровня изолированности разработчик должен найти золотую середину между потребностями в производительности и требованиями к целостности данных. Очевидно, что реально поддерживаемые уровни зависят от возможностей используемой СУБД.

При создании объекта Connection уровень его изолированности зависит от драйвера или БД. Можно вызвать метод setIsolationLevel, чтобы изменить уровень изолированности транзакций, и новое значение уровня будет установлено до конца сессии. Чтобы установить уровень изолированности только для одной транзакции, надо установить его перед выполнением транзакции и восстановить прежнее значение после ее завершения.

Типы уровней изолированности

  • TRANSACTION_NONE
    Транзакции не поддерживаются.
  • TRANSACTION_READ_COMMITTED
    Запрет на «грязное чтение» (dirty read). Данный уровень блокирует транзакциям чтение строк с неподтвержденными изменениями в них.
  • TRANSACTION_READ_UNCOMMITTED
    Разрешение на «dirty read». Данный уровень позволяет изменять строку с помощью одной транзакции и прочесть ее другой прежде, чем изменения в этой строке будут подтверждены (dirty read). Если изменения будут отменены с помощью rollback(), вторая транзакция вернет неправильную строку.
  • TRANSACTION_REPEATABLE_READ
    Запрет на «dirty read». Данный уровень препятствует транзакции от чтения строки с неподтвержденным изменением в ней, он также предотвращает ситуацию, когда одна транзакция читает строку, а вторая транзакция изменяет ее, при этом первая транзакция перечитывая строку, получает разные значения каждый раз (разовое чтение).
  • TRANSACTION_SERIALIZABLE
    Запрет на «dirty read». Данный уровень включает предотвращения из TRANSACTION_REPEATABLE_READ, более того предотвращает ситуацию, когда одна транзакция читает все строки, которые удовлетворяют условию WHERE, а вторая транзакция вставляет строку, которая удовлетворяет тому же условию WHERE, и первая транзакция, перечитывая с тем же условием, получает дополнительную «фантомную» строку при втором чтении.

Источник

Оцените статью