Spec-Zone .ru
спецификации, руководства, описания, API
|
Есть времена, когда Вы не хотите, чтобы один оператор вступил в силу, если другой не завершается. Например, когда владелец Перерыва на кофе обновляет количество кофе, продаваемого каждую неделю, владелец будет также хотеть обновить общую сумму, проданную до настоящего времени. Однако, количество, проданное в неделю и проданную общую сумму, должно быть обновлено одновременно; иначе, данные будут непоследовательны. Способ убедиться, что или оба действия происходят или никакое действие, происходит, должен использовать транзакцию. Транзакция является рядом той или большего количества операторов, который выполняется как модуль, таким образом, или все операторы выполняются, или ни один из операторов не выполняется.
Эта страница затрагивает следующие темы
Когда соединение создается, это находится в режиме автоматической фиксации. Это означает, что каждый отдельный SQL-оператор обрабатывается как транзакция и автоматически фиксируется прямо после того, как он выполняется. (Чтобы быть более точным, значение по умолчанию для SQL-оператора, который будет фиксироваться, когда оно завершается, не, когда оно выполняется. Оператор завершается, когда все его наборы результатов и количества обновления были получены. В почти всех случаях, однако, оператор завершается, и поэтому фиксируется, прямо после того, как он выполняется.)
Способ позволить двум или больше операторам, которые будут группироваться в транзакцию, состоит в том, чтобы отключить режим автоматической фиксации. Это демонстрируется в следующем коде, где con
активное соединение:
con.setAutoCommit(false);
После того, как режим автоматической фиксации отключается, никакие SQL-операторы не фиксируются, пока Вы не вызываете метод commit
явно. Все операторы, выполняемые после предыдущего звонка в метод commit
включаются в текущую транзакцию и фиксируются вместе как модуль. Следующий метод, CoffeesTable.updateCoffeeSales
, в котором con
активное соединение, иллюстрирует транзакцию:
public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException { PreparedStatement updateSales = null; PreparedStatement updateTotal = null; String updateString = "update " + dbName + ".COFFEES " + "set SALES = ? where COF_NAME = ?"; String updateStatement = "update " + dbName + ".COFFEES " + "set TOTAL = TOTAL + ? " + "where COF_NAME = ?"; try { con.setAutoCommit(false); updateSales = con.prepareStatement(updateString); updateTotal = con.prepareStatement(updateStatement); for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) { updateSales.setInt(1, e.getValue().intValue()); updateSales.setString(2, e.getKey()); updateSales.executeUpdate(); updateTotal.setInt(1, e.getValue().intValue()); updateTotal.setString(2, e.getKey()); updateTotal.executeUpdate(); con.commit(); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); if (con != null) { try { System.err.print("Transaction is being rolled back"); con.rollback(); } catch(SQLException excep) { JDBCTutorialUtilities.printSQLException(excep); } } } finally { if (updateSales != null) { updateSales.close(); } if (updateTotal != null) { updateTotal.close(); } con.setAutoCommit(true); } }
В этом методе режим автоматической фиксации отключается для соединения con
, что означает что два готовых оператора updateSales
и updateTotal
фиксируются вместе когда метод commit
вызывается. Всякий раз, когда commit
метод вызывают (или автоматически когда режим автоматической фиксации включается или явно когда это отключается), все изменения, следующие из операторов в транзакции, делаются постоянными. В этом случае это означает что SALES
и TOTAL
столбцы для колумбийского кофе были изменены на 50
(если TOTAL
был 0
ранее), и сохранит это значение, пока они не изменяются с другим оператором обновления.
Оператор con.setAutoCommit(true);
включает режиму автоматической фиксации, что означает, что каждый оператор еще раз фиксируется автоматически, когда это завершается. Затем, Вы вернулись к состоянию по умолчанию, где Вы не должны вызвать метод commit
самостоятельно. Желательно отключить режим автоматической фиксации только во время режима транзакции. Таким образом, Вы избегаете содержать блокировки базы данных для многократных операторов, который увеличивает вероятность конфликтов с другими пользователями.
В дополнение к собиранию в группу операторов для выполнения как модуль транзакции могут помочь сохранить целостность данных в таблице. Например, предположите, что сотрудник, как предполагалось, ввел новые цены на кофе в таблицу COFFEES
но задержанное выполнение этого в течение нескольких дней. Тем временем цены выросли, и сегодня владелец находится в процессе ввода более высоких цен. Сотрудник наконец находит время для ввода теперь устаревших цен в то же самое время, когда владелец пытается обновить таблицу. После вставки устаревших цен сотрудник понимает, что они больше не действительны, и вызывает Connection
метод rollback
отменить их эффекты. (Метод rollback
прерывает транзакцию и восстанавливает значения к тому, чем они были перед предпринятым обновлением.) Одновременно, владелец выполняет a SELECT
оператор и печать новых цен. В этой ситуации возможно, что владелец напечатает цену, которая откатывалась к ее предыдущему значению, делая печатную неправильную цену.
Этого вида ситуации можно избежать при использовании транзакций, обеспечивая некоторый уровень защиты против конфликтов, которые возникают когда два пользовательских данных доступа одновременно.
Чтобы избежать конфликтов во время транзакции, DBMS использует блокировки, механизмы для того, чтобы блокировать доступ другими к данным, к которым получает доступ транзакция. (Отметьте, что в режиме автоматической фиксации, где каждый оператор является транзакцией, блокировки сохранены только для одного оператора.) После того, как блокировка устанавливается, она остается в силе, пока транзакция не фиксируется или откатывается. Например, DBMS мог заблокировать строку таблицы, пока обновления к нему не фиксировались. Эффект этой блокировки состоял бы в том, чтобы препятствовать тому, чтобы пользователь получил грязное чтение, то есть, читая значение прежде, чем это будет сделано постоянным. (Доступ к обновленному значению, которое не фиксировалось, считают грязным чтением, потому что для того значения возможно откатываться к его предыдущему значению. Если Вы читаете значение, которое позже откатывается, Вы считаете недопустимое значение.)
То, как блокировки устанавливаются, определяется тем, что вызывают уровнем изоляции транзакции, который может расположиться от не поддержки транзакций вообще к поддержке транзакций, которые осуществляют очень строгие правила доступа.
Один пример уровня изоляции транзакции TRANSACTION_READ_COMMITTED
, который не будет позволять значению быть полученным доступ, пока оно не фиксировалось. Другими словами, если уровень изоляции транзакции устанавливается в TRANSACTION_READ_COMMITTED
, DBMS не позволяет грязным чтениям происходить. Интерфейс Connection
включает пять значений, которые представляют уровни изоляции транзакции, которые можно использовать в JDBC:
Уровень изоляции | Транзакции | Грязные чтения | Неповторяемые чтения | Фантомные Чтения |
---|---|---|---|---|
TRANSACTION_NONE |
Не поддерживаемый | Не применимый | Не применимый | Не применимый |
TRANSACTION_READ_COMMITTED |
Поддерживаемый | Предотвращенный | Позволенный | Позволенный |
TRANSACTION_READ_UNCOMMITTED |
Поддерживаемый | Позволенный | Позволенный | Позволенный |
TRANSACTION_REPEATABLE_READ |
Поддерживаемый | Предотвращенный | Предотвращенный | Позволенный |
TRANSACTION_SERIALIZABLE |
Поддерживаемый | Предотвращенный | Предотвращенный | Предотвращенный |
Неповторяемое чтение происходит, когда транзакция A получает строку, транзакция B впоследствии обновляет строку, и транзакцию, более позднее получает ту же самую строку снова. Транзакция A получает ту же самую строку дважды, но видит различные данные.
Фантомное чтение происходит, когда транзакция A получает ряд строк, удовлетворяющих данное условие, транзакция B впоследствии вставляет или обновляет строку так, что, строка теперь удовлетворяет условию в транзакции A, и транзакции более поздние повторения условное извлечение. Транзакция теперь видит дополнительную строку. Эта строка упоминается как фантом.
Обычно, Вы ничего не должны сделать об уровне изоляции транзакции; можно только использовать значение по умолчанию один для Вашего DBMS. Уровень изоляции транзакции значения по умолчанию зависит от Вашего DBMS. Например, для DB Java, это TRANSACTION_READ_COMMITTED
. JDBC позволяет Вам узнавать, в какой уровень изоляции транзакции Ваш DBMS устанавливается (использование Connection
метод getTransactionIsolation
) и также позволяет Вам устанавливать это в другой уровень (использующий Connection
метод setTransactionIsolation
).
Отметьте: драйвер JDBC не мог бы поддерживать все уровни изоляции транзакции. Если драйвер не поддерживает уровень изоляции, определенный в вызове setTransactionIsolation
, драйвер может заменить более высоким, более рестриктивным уровнем изоляции транзакции. Если драйвер не может заменить более высоким уровнем транзакции, он бросает a SQLException
. Используйте метод DatabaseMetaData.supportsTransactionIsolationLevel
определить, поддерживает ли драйвер данный уровень.
Метод Connection.setSavepoint
, наборы a Savepoint
объект в пределах текущей транзакции. Connection.rollback
метод перегружается, чтобы взять a Savepoint
параметр.
Следующий метод, CoffeesTable.modifyPricesByPercentage
, повышает цену определенного кофе на процент, priceModifier
. Однако, если новая цена больше чем указанная цена, maximumPrice
, тогда цена возвращается к исходной цене:
public void modifyPricesByPercentage( String coffeeName, float priceModifier, float maximumPrice) throws SQLException { con.setAutoCommit(false); Statement getPrice = null; Statement updatePrice = null; ResultSet rs = null; String query = "SELECT COF_NAME, PRICE FROM COFFEES " + "WHERE COF_NAME = '" + coffeeName + "'"; try { Savepoint save1 = con.setSavepoint(); getPrice = con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); updatePrice = con.createStatement(); if (!getPrice.execute(query)) { System.out.println( "Could not find entry " + "for coffee named " + coffeeName); } else { rs = getPrice.getResultSet(); rs.first(); float oldPrice = rs.getFloat("PRICE"); float newPrice = oldPrice + (oldPrice * priceModifier); System.out.println( "Old price of " + coffeeName + " is " + oldPrice); System.out.println( "New price of " + coffeeName + " is " + newPrice); System.out.println( "Performing update..."); updatePrice.executeUpdate( "UPDATE COFFEES SET PRICE = " + newPrice + " WHERE COF_NAME = '" + coffeeName + "'"); System.out.println( "\nCOFFEES table after " + "update:"); CoffeesTable.viewTable(con); if (newPrice > maximumPrice) { System.out.println( "\nThe new price, " + newPrice + ", is greater than the " + "maximum price, " + maximumPrice + ". Rolling back the " + "transaction..."); con.rollback(save1); System.out.println( "\nCOFFEES table " + "after rollback:"); CoffeesTable.viewTable(con); } con.commit(); } } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } finally { if (getPrice != null) { getPrice.close(); } if (updatePrice != null) { updatePrice.close(); } con.setAutoCommit(true); } }
Следующий оператор определяет что курсор ResultSet
объект, сгенерированный от getPrice
запрос закрывается когда commit
метод вызывают. Отметьте это, если Ваш DBMs не поддерживает ResultSet.CLOSE_CURSORS_AT_COMMIT
, тогда эта константа игнорируется:
getPrice = con.prepareStatement(query, ResultSet.CLOSE_CURSORS_AT_COMMIT);
Метод начинается, создавая a Savepoint
со следующим оператором:
Savepoint save1 = con.setSavepoint();
Метод проверяет, больше ли новая цена чем maximumPrice
значение. Если так, метод откатывает транзакцию со следующим оператором:
con.rollback(save1);
Следовательно, когда метод фиксирует транзакцию, вызывая Connection.commit
метод, это не будет фиксировать строк чей связанный Savepoint
откатывался; это будет фиксировать все другие обновленные строки.
Метод Connection.releaseSavepoint
берет a Savepoint
возразите в качестве параметра, и удаляет это из текущей транзакции.
После того, как точка сохранения была выпущена, пытаясь сослаться на нее в работе отката вызывает a SQLException
быть брошенным. Любые точки сохранения, которые были созданы в транзакции, автоматически выпускаются и становятся недопустимыми, когда транзакция фиксируется, или когда вся транзакция откатывается. Откат транзакции к точке сохранения автоматически выпускает и делает недопустимым любые другие точки сохранения, которые создавались после рассматриваемой точки сохранения.
Как отмечалось ранее, вызов метода rollback
завершает транзакцию и возвращает любые значения, которые были изменены к их предыдущим значениям. Если Вы пытаетесь выполнить один или более операторов в транзакции и получить a SQLException
, вызовите метод rollback
закончить транзакцию и запустить транзакцию снова и снова. Это - единственный способ знать то, что фиксировалось и что не фиксировалось. Ловля a SQLException
говорит Вам, что что-то является неправильным, но это не говорит Вам, что было или не фиксировалось. Поскольку невозможно рассчитывать на факт, что ничто не фиксировалось, вызывая метод rollback
единственный способ быть уверенным.
Метод CoffeesTable.updateCoffeeSales
демонстрирует транзакцию и включает a catch
блок, который вызывает метод rollback
. Если приложение продолжает и использует результаты транзакции, этот звонок rollback
метод в catch
блок предотвращает использование возможно неправильных данных.