首页 > 代码库 > jdbc事务管理

jdbc事务管理

 

 原文地址 《Using Transactions》,没事自己YY翻译

  • 事务?

事务是一个由单条或者多条语句所组成的执行单元,要么全部执行成功,否则都不执行。

 

  • 禁用jdbc事务的自动提交模式

当jdbc连接被创建时,是处于一个auto-commit(自动提交)的模式,这就意味着每个SQL语句都处于一个独立的事务中,在这些SQL语句被执行之后,事务会立即提交。(更准确的说,在默认情况下是一个SQL语句被提交时完成事务的提交,而不是在这条SQL被执行之后。但所有的结果集以及更新计数器已检索则表示该SQL语句已经被提交完成。然而,在几乎在大多数情况下,一条SQL语句提交完成并执行后,事务也完成提交)

想要将两条或多条SQL语句组合到一个事务中的方法就是禁用auto-commit(自动提交)的模式,当一个jdbc连接处于活动状态时,这就取决于下方的代码:

con.setAutoCommit(false);

 

  • 提交事务

在auto-commit(自动提交)模式被禁用之后,直到你明确调用commit方法之前任何SQL语句都不会完成提交操作。所有的语句都会在被合并到当前事务中来执行并作为一个整体事务单元进行提交。在下面的方法 CoffeesTable.updateCoffeeSales  中,con是当前一个被激活的jdbc连接,充分的描述了一个事务的全过程:

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);    }}

 

在上面的方法中,jdbc连接connection con禁用了auto-commit(自动提交)模式,这就意味着,两条prepared语句 updateSales 以及 updateTotal在调用了con连接的commit方法后被同时提交。无论commit方法何时被调用(即便是开启了事务的自动提交模式或者该模式被明确的禁用),所有在此事务中的语句所产生的变化都是持久的。此时,就表示 Colombian coffee 中的SALES 以及 TOTAL 这两个字段的值被修改为50(假设事先默认值是0),同时会保留这个值直到他们被另外一条update语句改变为止。

 

语句con.setAutoCommit(true); 启用了 auto-commit(自动提交)模式,意味着所有的SQL语句在完成提交时事务会自动提交。在此之后,你的程序将回到不需要自己调用commit方法的默认状态。这里建议仅在transaction模式下才禁用auto-commit。这样可以避免你在多SQL语句执行时挂起数据库锁,因为那会增加你与其他数据库访问者之间发生冲突的可能性。

 

  • 使用事务保持数据的完整性

当我们将SQL语句合并到一个事务中执行时,可以保证数据的完整性。

 

  • 设置和回滚到保存点

通过 Connection.setSavepoint方法,可以在当前事务中设置一个Savepoint对象, Connection.rollback 被重写并需要传入一个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);    }}

 

以下语句特别指明了getPrice查询所生成取结果集游标在调用了commit方法后被关闭了。特别声明,假如的所使用的数据库不支持resultset.close_cursors_at_commit,这些操作将被忽略:

getPrice = con.prepareStatement(query, ResultSet.CLOSE_CURSORS_AT_COMMIT);

 

方法中通过下列语句创建了一个Savepoint

Savepoint save1 = con.setSavepoint();

 

方法中检查了新的price是否大于maximumPrice的值,如果是,方法将通过以下语句进行事务回滚:

con.rollback(save1);

 

因此,当该方法通过调用Connection.commit提交transaction时,程序不会提交任何已经被回滚到Savepoint的行,而是提交其它更新的行。

 

  • 释放保存点

方法 Connection.releaseSavepoint传入了一个Savepoint对象作为参数并将其从当前事务中移除。在savepoint被释放之后,若试图去调用该savepoint对象将会触发一个SQLException被抛出。任何一个已经创建的savepoints都是在一个事务中自动释放,并且在该事务被提交或回滚整个事务操作之后失效。

 

  • 什么时候应该调用rollback方法

  正如之前所提到的,调用rollback方法来终止一个事务操作并返回,将会修改变量此前的值。若你试图在当前事务中继续执行一条或者多条语句,将会引发一个SQLException,调用rollback方法终止事务并重新开始一个事务。That is the only way to know what has been committed and what has not been committed.(这句话如何翻译?),捕获SQLException可以让你知道程序出错了,但是这并不意味着你可以知道哪些操作被提交,而哪些没有。调用rollback是唯一方法。

  CoffeesTable.updateCoffeeSales方法充分解释了一个事务的操作以及在将调用rollback方法中包含进一个catch块中,加入应用程序继续执行并使用transaction中的执行结果,程序将会在可能引发非正常数据操作之前通过catch块中调用rollback方法进行事务回滚。