【问题标题】:Nested transactions - Rollback scenario嵌套事务 - 回滚场景
【发布时间】:2011-04-16 17:45:05
【问题描述】:
A(){
    con.begin;
    .........
    .........
    B();
    ........
    ........(con.rollback;)
    con.commit;
    }

    B{
    con.begin;
    .......
    .......
    con.commit;
    }

在上面的代码中,我在 A() 处开始了一个新的 DB 事务。它成功地执行了一些事务。之后 B() 开始执行,它也成功执行了一些事务,现在控制返回到 A()。此时发生了一些异常,我进行了回滚。我想知道B()中成功的事务是否会回滚。

【问题讨论】:

标签: java postgresql


【解决方案1】:

简短的回答,不。长答案如下。

Java 中对嵌套事务的支持取决于各种变量。

支持 JTA 中的嵌套事务

首先,如果您使用 JTA,则由事务管理器来支持嵌套事务。如果尝试在已与事务关联的线程中启动新事务,则任何开始事务的尝试都可能导致事务管理器(不支持嵌套事务)抛出 NotSupportedException。

来自 Java Transaction API 1.1 规范:

3.2.1 开始交易

TransactionManager.begin 方法启动 全球交易和联营公司 事务上下文与 调用线程。如果交易 经理实施不 支持嵌套事务, TransactionManager.begin 方法抛出 NotSupportedException 当调用线程已经 与交易相关联。

支持 JDBC 中的嵌套事务

JDBC 3.0 引入了Savepoint 类,它或多或少类似于数据库中保存点的概念。必须使用返回保存点实例的Connection.setSavepoint() 方法初始化保存点。稍后可以使用Connection.rollback(Savepoint svpt) 方法回滚到此保存点。当然,所有这一切都取决于您是否使用符合 JDBC 3.0 的驱动程序,该驱动程序支持设置保存点并回滚到它们。

自动提交的影响

默认情况下,所有获得的连接都设置为自动提交,除非 JDBC 驱动程序在这方面有明显的偏差。如果启用此功能,则会自动排除嵌套事务的范围,因为通过连接在数据库中所做的所有更改都会在执行时自动提交。

如果您禁用自动提交功能,并选择显式提交和回滚事务,则提交事务始终会提交连接执行的所有更改,直到该时间点。请注意,选择提交的更改不能由程序员定义 - 直到该时刻的所有更改都被选择提交,无论它们是否已以一种方法或另一种方法执行。唯一的出路是定义保存点,或者破解 JDBC 驱动程序 - 驱动程序通常提交由与线程关联的连接执行的所有更改,因此启动一个新线程(这很糟糕)并在其中获取一个新连接,通常会给你一个新的事务上下文。

您可能还想检查您的框架如何为嵌套事务提供支持,尤其是当您与 JDBC API 隔离或无法自行启动新的 JTA 事务时。


根据上面对如何在各种场景中实现嵌套事务支持的描述,您的代码中的回滚似乎将回滚与 Connection 对象关联的所有更改。

【讨论】:

    【解决方案2】:

    恐怕这看起来像是糟糕的交易管理。如果您改为处理从调用者到 A 和 B 的提交和回滚,那就太好了。

    
    A()
    {
     //business code A
     B();
     //more business code A
    }
    
    B()
    {
      //business code B
    }
    
    DoA()
    {
      try
      {
         con.begin();
         A();
         con.commit();
      }
      catch(Exception e)
      {
         con.rollback();
      }
    }
    
    DoB()
    {
      try
      {
         con.begin();
         B();
         con.commit();
      }
      catch(Exception e)
      {
         con.rollback();
      }
    }
    

    【讨论】:

      【解决方案3】:

      根据您的代码,您在 A() 中开始交易。然后跳转到 B() 再次开始事务,这反过来将提交所有先前的事务。然后在 B() 结束时,事务被显式提交。此时,您的所有代码都已提交。现在代码返回到 A() 并处理剩余的代码。如果出现异常,只会回滚 B() 调用后的这部分。

      【讨论】:

        【解决方案4】:

        您可以在 Postgres 8 及更高版本中使用 Java.SQL 的内置 SavePoint 函数。

        Connection conn = null;
        Savepoint save = null;
        DatabaseManager mgr = DatabaseManager.getInstance();
        try {
            conn = mgr.getConnection();
            proc = conn.prepareCall("{ call writeStuff(?, ?) }");
        
            //Set DB parameters
            proc.setInt(1, stuffToSave);
            proc.setString(2, moreStuff);
        
            //Set savepoint here:
            save = conn.setSavepoint();
        
            //Try to execute the query
            proc.execute();
        
            //Release the savepoint, otherwise buffer memory will be eaten
            conn.releaseSavepoint(save);
        
        } catch (SQLException e) {
            //You may want to log the first one only.
            //This block will attempt to rollback
            try {
                //Rollback to the Savepoint of prior transaction:
                conn.rollback(save);
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }
        

        当发生 SQL 异常时,当前事务回滚到 SavePoint,剩余事务可能会发生。如果没有回滚,后续事务将失败。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-08-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-04-11
          • 1970-01-01
          相关资源
          最近更新 更多