【问题标题】:Java: call a function automatically at scope exit (like c++ destructors)Java:在范围退出时自动调用函数(如 c++ 析构函数)
【发布时间】:2011-10-27 14:26:28
【问题描述】:

对于 mysql 连接,我有一个连接对象并使用事务机制 connection.startTransaction()connection.commitTransaction()connection.rollbackTransaction()

对于每个startTransaction(),必须始终调用commitTransaction()rollbackTransaction()。错过这样一个电话或同时拨打这两个电话会破坏我的交易系统。

所以我按以下方式使用它们:

boolean i_am_in_a_transaction=true;
try {
    connection.startTransaction();
    ...
    i_am_in_a_transaction=false;
    connection.commitTransaction();
} finally {
    if(i_am_in_a_transaction) {
        connection.rollbackTransaction();
    }
}

这确保了声明的调用顺序,但它很费力,因为我必须在使用事务的任何地方都写这行。

在 C++ 中,如果调用了 commit() 函数,我将使用一个事务对象检查其析构函数,否则调用 rollback()

class Transaction {
    public:
        Transaction()
            :am_in_transaction(false) {
        }

        ~Transaction() {
            if(_am_in_transaction) {
                rollback();
            }
        }

        void startTransaction() {
            _am_in_transaction=true;
            ...start Transaction...
        }

        void commit() {
            _am_in_transaction=false;
            ...commit Transaction...
        }

        void rollback() {
            _am_in_transaction=false;
            ...rollback Transaction...
        }

    private:
        bool _am_in_transaction;
}

这样我就可以在一个地方实现逻辑并且可以非常简单地使用它:

Transaction my_transaction;
my_transaction.startTransaction;
...
my_transaction.commit();

这段代码比上面带有 try/finally 块的 java 代码要简单得多。

有没有办法在 java 中实现这种行为,而无需将逻辑专用于调用者并让他实现 try/finally 块?

在范围退出时自动调用函数之类的方法会对我有所帮助。

【问题讨论】:

标签: java jdbc


【解决方案1】:

与 C++ 中的析构函数类似(非常重要)的是方法finalize()。不同之处在于无法保证垃圾收集器何时真正调用它,因此不建议依赖它。

否则,你能做的最好的就是try/finally 块。

【讨论】:

  • 更多,最多调用一次。所以即使你下次救出对象也不会执行finalize。
  • finalize() 不是析构函数。 JVM负责调用它。如果需要,它会在需要时调用它。因此,强烈建议不要将任何逻辑基于 finalize()。它只能用于后备。
【解决方案2】:

Java 没有析构函数。 但是有几个“标准”解决方案可以帮助您。

1.使用名为“模板方法”的模式。

abstract class Base {
    public query() {
         openTransaction();
         doQuery();
         closeTransaction();
    }
    protected abstract doQuery();
}

现在您应该在您创建的每个子类中实现doQuery(),并通过从基类调用“query()”来使用它。

2.使用面向方面的编程

3.使用装饰器(包装器)模式。

4. 使用一种流行的 ORM 框架(Hibernate、iBatis 等)为您解决所有这些问题,并且完全不处理低级 JDBC 的东西。

【讨论】:

  • 我会添加 #5,使用 Spring 的内置机制,它是 #2、#3 和 #4 的组合。
  • @Dave Newton,我同意你的观点,但我认为它是#4 的一部分
  • 有点,但它们可以在 Spring 之外使用,因此有区别:)
【解决方案3】:

想到了几个解决方案...

正如@Dark Falcon 指出的那样,这对于try with resources 调用将是一个很好的用例,它将在尝试结束时自动清理您的资源。不幸的是,这仅在 Java 7 中可用。

Java 类确实定义了一个 finalize() 方法,当对象被垃圾回收时可以调用该方法,但重写此方法几乎从来都不是正确的做法。

如果你迷上了“在函数返回时执行代码”这个想法,我认为你唯一的选择就是使用Aspect Oriented Programming。如果您阅读了诸如AspectJ 之类的一些包或查看使用AOP with Spring,您可以做一些配置魔术,通过拦截调用来获取函数返回时执行的代码。这是 an example 在函数返回时使用 Spring AOP 执行其他代码。

【讨论】:

    【解决方案4】:

    如果可以选择更新到 java 7,则新的 try with resources 将在 Closable 实现中执行方法 close

    【讨论】:

      【解决方案5】:

      我只有一种方法,而且只做一次。

      public static void update(Connection connection, String updateSQL) {
          PreparedStatement update = null;
          try {
            try {
              connection.startTransaction();
              update = connection.prepareStatement(updateString);
              update.executeUpdate();
            } finally {
              connection.rollbackTransaction();
            }
          connection.commitTransaction();
          } finally {
              if(update != null) update.close();
          }
      }
      

      稍后

      update(connection, updateSQL1);
      update(connection, updateSQL2);
      // etc.
      

      【讨论】:

      • 我不太了解你的代码(最后总是被执行——即使没有抛出异常),但我想我明白了你的想法。这在事务中只有一个 sql 语句时有效。但一个事务通常由多个语句组成。
      • 在这种情况下,您可以传递一个 List 或String... 的 SQL 语句。
      • 除了它们相互依赖
      • 不确定更新如何相互依赖。有些是根据其他是否失败有条件地执行,但你不能用 SQL 写这个?但是如果你需要这个,你可以重复调用 update()。
      • 你是对的 - 当只有更新语句时,这种方法有效。
      【解决方案6】:

      我知道这是一个老问题,但为了他人的利益:

      您可以使用实现接口的匿名内部类来完成这项工作,类似于 Java 比较器和列表排序机制的工作方式。这允许您将内部类视为执行范围。

      例如修改你原来的例子:

      class Transaction
      {
         boolean i_am_in_a_transaction=false;
      
         interface AutoRollback
         {
             void runQueries() throws Throwable;
         }
      
          void startTransaction() {
              i_am_in_a_transaction=true;
              ...start Transaction...
          }
      
          void commit() {
              i_am_in_a_transaction=false;
              ...commit Transaction...
          }
      
          void rollback() {            
              i_am_in_a_transaction=false;
              ...rollback Transaction...
          }
      
         public void execute(AutoRollback work)
         {
            try {
                work.runQueries();
            } catch ( Throwable t ) {
                rollback();
                throw t;
            }
         }
      }
      

      然后是如何使用它的示例:

      void test() throws WhateverException
      {
          Transaction my_transaction;
          my_transaction.startTransaction();
      
          my_transaction.execute( new AutoRollback() { public void runQueries() throws Throwable {
      
           ... perform your queries: can be more than one, complex code, etc. ...
           ... local variables from the enclosing scope can be used as long as they are final... 
      
          }});
      
          my_transaction.commit();
      }
      

      如果您有 Java 8,使用 lambdas 会变得更漂亮,因为它保存了 new AutoRollback 语法。

      如果您没有 Java 8 并且过多的标点符号仍然困扰着您,您应该能够使用注解处理器和代码注入使其读起来更漂亮。 编译时注解设置为 LOCAL_VARIABLE 的目标是您想要的,然后将其应用于my_transaction

      ...假设您的工作场所允许使用 apt 或预处理器等注释处理器,并且您希望为语法糖做那么多工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-06-14
        • 2012-09-26
        • 1970-01-01
        • 1970-01-01
        • 2018-02-06
        • 2018-09-15
        • 2016-09-26
        相关资源
        最近更新 更多