The Java Tutorials have been written for JDK 8.Java教程是为JDK 8编写的。Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available.本页中描述的示例和实践没有利用后续版本中引入的改进,并且可能使用不再可用的技术。See Java Language Changes for a summary of updated language features in Java SE 9 and subsequent releases.有关Java SE 9及其后续版本中更新的语言特性的摘要,请参阅Java语言更改。
See JDK Release Notes for information about new features, enhancements, and removed or deprecated options for all JDK releases.有关所有JDK版本的新功能、增强功能以及已删除或不推荐的选项的信息,请参阅JDK发行说明。
There are times when you do not want one statement to take effect unless another one completes. 有时,您不希望一条语句生效,除非另一条语句完成。For example, when the proprietor of The Coffee Break updates the amount of coffee sold each week, the proprietor will also want to update the total amount sold to date. 例如,当咖啡休息时间的所有者更新每周售出的咖啡量时,所有者还将希望更新到目前为止售出的咖啡总量。However, the amount sold per week and the total amount sold should be updated at the same time; otherwise, the data will be inconsistent. 但是,每周的销售额和总销售额应同时更新;否则,数据将不一致。The way to be sure that either both actions occur or neither action occurs is to use a transaction. 确保两个操作都发生或两个操作都不发生的方法是使用事务。A transaction is a set of one or more statements that is executed as a unit, so either all of the statements are executed, or none of the statements is executed.事务是一组作为一个单元执行的一个或多个语句,因此要么执行所有语句,要么不执行任何语句。
This page covers the following topics本页涵盖以下主题
When a connection is created, it is in auto-commit mode. 创建连接时,它处于自动提交模式。This means that each individual SQL statement is treated as a transaction and is automatically committed right after it is executed. 这意味着每个单独的SQL语句都被视为一个事务,并在执行后立即自动提交。(To be more precise, the default is for a SQL statement to be committed when it is completed, not when it is executed. (更准确地说,默认情况是SQL语句在完成时提交,而不是在执行时提交。A statement is completed when all of its result sets and update counts have been retrieved. 当检索到一条语句的所有结果集和更新计数时,该语句即告完成。In almost all cases, however, a statement is completed, and therefore committed, right after it is executed.)但是,在几乎所有情况下,语句都是在执行后立即完成并提交的。)
The way to allow two or more statements to be grouped into a transaction is to disable the auto-commit mode. 允许将两个或多个语句分组到一个事务中的方法是禁用自动提交模式。This is demonstrated in the following code, where 以下代码对此进行了说明,其中con
is an active connection:con
为活动连接:
con.setAutoCommit(false);
After the auto-commit mode is disabled, no SQL statements are committed until you call the method 禁用自动提交模式后,在显式调用commit
explicitly. commit
方法之前,不会提交任何SQL语句。All statements executed after the previous call to the method 上次调用commit
are included in the current transaction and committed together as a unit. commit
方法后执行的所有语句都包含在当前事务中,并作为一个单元一起提交。The following method, 以下方法CoffeesTable.updateCoffeeSales
, in which con
is an active connection, illustrates a transaction:CoffeesTable.updateCoffeeSales
说明了一个事务,其中con
是活动连接:
public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException { String updateString = "update COFFEES set SALES = ? where COF_NAME = ?"; String updateStatement = "update COFFEES set TOTAL = TOTAL + ? where COF_NAME = ?"; try (PreparedStatement updateSales = con.prepareStatement(updateString); PreparedStatement updateTotal = con.prepareStatement(updateStatement)) { con.setAutoCommit(false); 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); } } } }
In this method, the auto-commit mode is disabled for the connection 在此方法中,连接con
, which means that the two prepared statements updateSales
and updateTotal
are committed together when the method commit
is called. con
禁用了自动提交模式,这意味着调用方法commit
时,两个准备好的语句updateSales
和updateTotal
一起提交。Whenever the 每当调用commit
method is called (either automatically when auto-commit mode is enabled or explicitly when it is disabled), all changes resulting from statements in the transaction are made permanent. commit
方法时(无论是在启用自动提交模式时自动调用,还是在禁用该方法时显式调用),事务中的语句所导致的所有更改都是永久性的。In this case, that means that the 在本例中,这意味着哥伦比亚咖啡的SALES
and TOTAL
columns for Colombian coffee have been changed to 50
(if TOTAL
had been 0
previously) and will retain this value until they are changed with another update statement.SALES
和TOTAL
列已更改为50
(如果之前TOTAL
为0
),并将保留此值,直到使用另一个更新语句更改为止。
The statement 语句con.setAutoCommit(true);
enables auto-commit mode, which means that each statement is once again committed automatically when it is completed. con.setAutoCommit(true);
启用自动提交模式,这意味着每个语句在完成时将再次自动提交。Then, you are back to the default state where you do not have to call the method 然后,您回到默认状态,在那里您不必自己调用commit
yourself. commit
方法。It is advisable to disable the auto-commit mode only during the transaction mode. 建议仅在事务模式期间禁用自动提交模式。This way, you avoid holding database locks for multiple statements, which increases the likelihood of conflicts with other users.通过这种方式,您可以避免为多个语句持有数据库锁,这会增加与其他用户发生冲突的可能性。
In addition to grouping statements together for execution as a unit, transactions can help to preserve the integrity of the data in a table. 除了将语句分组作为一个单元执行外,事务还有助于保持表中数据的完整性。For instance, imagine that an employee was supposed to enter new coffee prices in the table 例如,假设一名员工本应在COFFEES
but delayed doing it for a few days. COFFEES
表上输入新的咖啡价格,但却延迟了几天。In the meantime, prices rose, and today the owner is in the process of entering the higher prices. 与此同时,价格上涨,今天业主正在进入更高的价格。The employee finally gets around to entering the now outdated prices at the same time that the owner is trying to update the table. 在所有者试图更新表的同时,员工终于开始输入现在已经过时的价格。After inserting the outdated prices, the employee realizes that they are no longer valid and calls the 插入过期价格后,员工意识到这些价格不再有效,并调用Connection
method rollback
to undo their effects. Connection
方法rollback
撤消其效果。(The method (rollback
aborts a transaction and restores values to what they were before the attempted update.) rollback
方法中止事务并将值恢复到尝试更新之前的值。)At the same time, the owner is executing a 同时,所有者正在执行SELECT
statement and printing the new prices. SELECT
语句并打印新的价格。In this situation, it is possible that the owner will print a price that had been rolled back to its previous value, making the printed price incorrect.在这种情况下,所有者可能会打印已回滚到其先前值的价格,从而使打印的价格不正确。
This kind of situation can be avoided by using transactions, providing some level of protection against conflicts that arise when two users access data at the same time.通过使用事务可以避免这种情况,为两个用户同时访问数据时产生的冲突提供一定程度的保护。
To avoid conflicts during a transaction, a DBMS uses locks, mechanisms for blocking access by others to the data that is being accessed by the transaction. 为了避免事务期间的冲突,DBMS使用锁,这是一种阻止其他人访问事务正在访问的数据的机制。(Note that in auto-commit mode, where each statement is a transaction, locks are held for only one statement.) (请注意,在自动提交模式中,每个语句都是事务,锁只为一个语句持有。)After a lock is set, it remains in force until the transaction is committed or rolled back. 设置锁后,它将保持有效,直到提交或回滚事务。For example, a DBMS could lock a row of a table until updates to it have been committed. 例如,DBMS可以锁定表中的一行,直到提交更新为止。The effect of this lock would be to prevent a user from getting a dirty read, that is, reading a value before it is made permanent. 这个锁的作用是防止用户获得脏读,也就是说,在值被永久化之前读取它。(Accessing an updated value that has not been committed is considered a dirty read because it is possible for that value to be rolled back to its previous value. (访问尚未提交的更新值被视为脏读,因为该值可能回滚到其以前的值。If you read a value that is later rolled back, you will have read an invalid value.)如果读取了稍后回滚的值,则将读取无效值。)
How locks are set is determined by what is called a transaction isolation level, which can range from not supporting transactions at all to supporting transactions that enforce very strict access rules.锁的设置方式由所谓的事务隔离级别决定,该级别可以是完全不支持事务,也可以是支持执行非常严格的访问规则的事务。
One example of a transaction isolation level is 事务隔离级别的一个示例是TRANSACTION_READ_COMMITTED
, which will not allow a value to be accessed until after it has been committed. TRANSACTION_READ_COMMITTED
,它在提交值之前不允许访问该值。In other words, if the transaction isolation level is set to 换句话说,如果事务隔离级别设置为TRANSACTION_READ_COMMITTED
, the DBMS does not allow dirty reads to occur. TRANSACTION_READ_COMMITTED
,则DBMS不允许脏读发生。The interface 接口Connection
includes five values that represent the transaction isolation levels you can use in JDBC:Connection
包括五个值,它们表示可以在JDBC中使用的事务隔离级别:
TRANSACTION_NONE |
||||
TRANSACTION_READ_COMMITTED |
||||
TRANSACTION_READ_UNCOMMITTED |
||||
TRANSACTION_REPEATABLE_READ |
||||
TRANSACTION_SERIALIZABLE |
A non-repeatable read occurs when transaction A retrieves a row, transaction B subsequently updates the row, and transaction A later retrieves the same row again. 当事务A检索行,事务B随后更新行,事务A稍后再次检索同一行时,将发生不可重复读取。Transaction A retrieves the same row twice but sees different data.事务A检索同一行两次,但看到不同的数据。
A phantom read occurs when transaction A retrieves a set of rows satisfying a given condition, transaction B subsequently inserts or updates a row such that the row now meets the condition in transaction A, and transaction A later repeats the conditional retrieval. 当事务A检索满足给定条件的一组行,事务B随后插入或更新一行,使该行现在满足事务A中的条件,事务A随后重复条件检索时,会发生幻像读取。Transaction A now sees an additional row. 事务A现在会看到另一行。This row is referred to as a phantom.这一行被称为幻影。
Usually, you do not need to do anything about the transaction isolation level; you can just use the default one for your DBMS. 通常,您不需要对事务隔离级别做任何事情;您可以只为您的DBMS使用默认值。The default transaction isolation level depends on your DBMS. 默认事务隔离级别取决于您的DBMS。For example, for Java DB, it is 例如,对于Java DB,它是TRANSACTION_READ_COMMITTED
. TRANSACTION_READ_COMMITTED
。JDBC allows you to find out what transaction isolation level your DBMS is set to (using the JDBC允许您找出DBMS设置为哪个事务隔离级别(使用Connection
method getTransactionIsolation
) and also allows you to set it to another level (using the Connection
method setTransactionIsolation
).Connection
方法getTransactionIsolation
),还允许您将其设置为另一个级别(使用Connection
方法setTransactionIsolation
)。
Note: A JDBC driver might not support all transaction isolation levels. 注意:JDBC驱动程序可能不支持所有事务隔离级别。If a driver does not support the isolation level specified in an invocation of 如果驱动程序不支持在调用setTransactionIsolation
, the driver can substitute a higher, more restrictive transaction isolation level. setTransactionIsolation
时指定的隔离级别,则该驱动程序可以替换更高、更严格的事务隔离级别。If a driver cannot substitute a higher transaction level, it throws a 如果驱动程序无法替换更高的事务级别,则会抛出SQLException
. SQLException
。Use the method 使用DatabaseMetaData.supportsTransactionIsolationLevel
to determine whether or not the driver supports a given level.DatabaseMetaData.supportsTransactionIsolationLevel
方法确定驱动程序是否支持给定级别。
The method 方法Connection.setSavepoint
, sets a Savepoint
object within the current transaction. Connection.setSavepoint
在当前事务中设置保存点对象。The 重载Connection.rollback
method is overloaded to take a Savepoint
argument.Connection.rollback
方法以获取保存点参数。
The following method, 以下方法CoffeesTable.modifyPricesByPercentage
, raises the price of a particular coffee by a percentage, priceModifier
. CoffeesTable.modifyPricesByPercentage
将特定咖啡的价格提高一个百分比priceModifier
。However, if the new price is greater than a specified price, 但是,如果新价格大于指定价格maximumPrice
, then the price is reverted to the original price:maximumPrice
,则该价格将恢复为原始价格:
public void modifyPricesByPercentage( String coffeeName, float priceModifier, float maximumPrice) throws SQLException { con.setAutoCommit(false); ResultSet rs = null; String priceQuery = "SELECT COF_NAME, PRICE FROM COFFEES " + "WHERE COF_NAME = ?"; String updateQuery = "UPDATE COFFEES SET PRICE = ? " + "WHERE COF_NAME = ?"; try (PreparedStatement getPrice = con.prepareStatement(priceQuery, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); PreparedStatement updatePrice = con.prepareStatement(updateQuery)) { Savepoint save1 = con.setSavepoint(); getPrice.setString(1, coffeeName); if (!getPrice.execute()) { 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.printf("Old price of %s is $%.2f%n", coffeeName, oldPrice); System.out.printf("New price of %s is $%.2f%n", coffeeName, newPrice); System.out.println("Performing update..."); updatePrice.setFloat(1, newPrice); updatePrice.setString(2, coffeeName); updatePrice.executeUpdate(); System.out.println("\nCOFFEES table after update:"); CoffeesTable.viewTable(con); if (newPrice > maximumPrice) { System.out.printf("The new price, $%.2f, is greater " + "than the maximum price, $%.2f. " + "Rolling back the transaction...%n", newPrice, maximumPrice); con.rollback(save1); System.out.println("\nCOFFEES table after rollback:"); CoffeesTable.viewTable(con); } con.commit(); } } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } finally { con.setAutoCommit(true); } }
The following statement specifies that the cursor of the 下面的语句指定在调用ResultSet
object generated from the getPrice
query is closed when the commit
method is called. commit
方法时关闭从getPrice
查询生成的ResultSet
对象的游标。Note that if your DBMs does not support 请注意,如果DBMs不支持ResultSet.CLOSE_CURSORS_AT_COMMIT
, then this constant is ignored:ResultSet.CLOSE_CURSORS_AT_COMMIT
,则忽略此常量:
getPrice = con.prepareStatement(query, ResultSet.CLOSE_CURSORS_AT_COMMIT);
The method begins by creating a 该方法首先使用以下语句创建Savepoint
with the following statement:Savepoint
:
Savepoint save1 = con.setSavepoint();
The method checks if the new price is greater than the 该方法检查新价格是否大于maximumPrice
value. maximumPrice
值。If so, the method rolls back the transaction with the following statement:如果是,该方法将使用以下语句回滚事务:
con.rollback(save1);
Consequently, when the method commits the transaction by calling the 因此,当该方法通过调用Connection.commit
method, it will not commit any rows whose associated Savepoint
has been rolled back; it will commit all the other updated rows.Connection.commit
方法提交事务时,它不会提交其关联的Savepoint
已回滚的任何行;它将提交所有其他更新的行。
The method 方法Connection.releaseSavepoint
takes a Savepoint
object as a parameter and removes it from the current transaction.Connection.releaseSavepoint
将Savepoint
对象作为参数,并将其从当前事务中删除。
After a savepoint has been released, attempting to reference it in a rollback operation causes a 释放保存点后,尝试在回滚操作中引用它会导致引发SQLException
to be thrown. SQLException
。Any savepoints that have been created in a transaction are automatically released and become invalid when the transaction is committed, or when the entire transaction is rolled back. 在事务中创建的任何保存点都会自动释放,并在提交事务或回滚整个事务时失效。Rolling a transaction back to a savepoint automatically releases and makes invalid any other savepoints that were created after the savepoint in question.将事务回滚到保存点会自动释放并使在相关保存点之后创建的任何其他保存点无效。
As mentioned earlier, calling the method 如前所述,调用rollback
terminates a transaction and returns any values that were modified to their previous values. rollback
方法终止事务并返回修改为以前值的所有值。If you are trying to execute one or more statements in a transaction and get a 如果试图在事务中执行一个或多个语句并获取SQLException
, call the method rollback
to end the transaction and start the transaction all over again. SQLException
,请调用rollback
方法结束事务并重新启动事务。That is the only way to know what has been committed and what has not been committed. 这是了解已经犯下什么和没有犯下什么的唯一途径。Catching a 捕获SQLException
tells you that something is wrong, but it does not tell you what was or was not committed. SQLException
会告诉您发生了错误,但它不会告诉您发生了什么或没有发生什么。Because you cannot count on the fact that nothing was committed, calling the method 因为您不能指望什么都没有提交,所以调用rollback
is the only way to be certain.rollback
方法是唯一确定的方法。
The method 方法CoffeesTable.updateCoffeeSales
demonstrates a transaction and includes a catch
block that invokes the method rollback
. CoffeesTable.updateCoffeeSales
演示了一个事务,并包含一个调用方法rollback
的catch
块。If the application continues and uses the results of the transaction, this call to the 如果应用程序继续并使用事务的结果,那么对rollback
method in the catch
block prevents the use of possibly incorrect data.catch
块中的rollback
方法的调用将防止使用可能不正确的数据。