【问题标题】:Roll back transaction and close connection on thrown exceptions回滚事务并在抛出异常时关闭连接
【发布时间】:2013-09-26 01:17:15
【问题描述】:

目前在具有本地和远程 EJB、MDB(单例和无状态)的 JavaEE 应用程序服务器中,我正在为 Hibernate Core 使用 JDBC-Transactions。

管理自己的所有打开和关闭,提交休眠会话和事务可能会导致连接泄漏和未提交的事务。
特别是在编程错误导致自定义或未经检查的异常未被捕获并抛出给远程客户端的情况下。

在发生错误时确保关闭休眠会话并回滚事务的最简单或最佳方法是什么?

使用容器管理事务 (CMT) 还是可以在任何 EJB 方法返回时调用的拦截器中关闭会话?

一种简单的方法是将会话范围的用法包装在 try-catch 块中并捕获任何类型的异常,但更倾向于使用更少代码的通用方法。

编辑:远程 EJB 示例

  • 我的低级 Hibernate DAO 确实会关闭连接并在抛出异常时回滚事务。如果连接仍然打开,问题是 DAO 访问之间的业务逻辑。*

    public void doSomething(Foo foo) throws Exception
    {
        // open session and transaction
        Session session = DAO.openSession();
    
        // retrieve data
        Bar bar = DAO.get(session, ...) 
    
        // call other methods which throws an exception resulting in open connection
        doOtherStuff(foo, bar)
    
        DAO.save(session, foo);
    
        // commit transaction
        DAO.closeAndCommitSession(session);
    }
    

现在我正在使用一个大的 try-catch-finally:

    public void doSomething(Foo foo) throws Exception
    {
        // open session and transaction
        Session session = DAO.openSession();
        try
        {
            // retrieve data
            Bar bar = DAO.get(session, ...) 

            // call other methods which throws an exception resulting in open connection
            doOtherStuff(foo, bar)

            DAO.save(session, foo);
        }
        catch (final Exception e)
        {
            DAO.rollBackTransaction(session);
            throw e;
        }
        finally
        {
            DAO.closeAndCommitSession(session);
        }
    }

【问题讨论】:

    标签: hibernate ejb ejb-3.0


    【解决方案1】:

    一般来说,这个问题是关于资源管理 正确简单的方式。这始终需要两个要素:一个简单的 API 和在任何地方使用此 API 的规则。

    可能不是针对您使用 Hibernate 的用例的具体解决方案,而只是为了展示总体思路:使用 贷款模式

    public interface ConnectionRunnable {
        public void run(Connection conn) throws SQLException;
    }
    

    如果您使用 Groovy 或 Scala(带有闭包),这段代码会更加优雅。在这种情况下,不需要此接口。

    不管怎样,无论你在哪里需要连接,都可以使用这样的东西:

        public static void withConnection(final String dataSourceName,
                ConnectionRunnable runnable) throws SQLException, NamingException {
            final InitialContext ic = new InitialContext();
            final DataSource ds = (DataSource) ic.lookup(dataSourceName);
    
            final Connection conn = ds.getConnection();
            try {
                runnable.run(conn);
            } finally {
                conn.close();
            }
        }
    

    在您的 EJB 中像这样使用:

    ConnectionUtils.withConnection("java:/jdbc/sandbox", new ConnectionRunnable() {
        public void run(Connection conn) throws SQLException {
            // Do something with the connection
        }
    });
    
    • 使用贷款模式,您不会忘记关闭连接
    • 如果是 SQLExceptionNamingException,您可以集中决定如何正确处理它:使用 BMT 手动提交或回滚。使用setRollbackOnly或抛出SystemException来触发容器(使用CMT)等。如果没有特殊原因,使用CMT。
    • 即使您稍后更改错误处理,也只是一个代码片段。

    更新:使用ConnectionRunnable 接口的开销。

    ConnectionRunnable 只是“打字开销”:Groovy 和 Scala 以相同的方式实现闭包,运行时开销可以忽略不计。 Java 8 可能会以同样的方式实现闭包。

    在这种情况下,提交或回滚数据库事务的成本无论如何都会高得多。

    从 Java/过程/通常的角度来看,这看起来很奇怪,而关于异常处理的代码重用的想法也可能看起来很奇怪。

    使用 Scala 在使用网站上看起来像这样(直译):

    ConnectionUtils.withConnection("java:/jdbc/sandbox") {
      conn =>
        // Do something with the connection
    }
    

    但在底层它也使用了类似 JVM 上的匿名类。

    【讨论】:

    • 这对于每个方法都需要一个ConnectionRunnable,这似乎是非常大的开销。关闭连接是最低优先级,因为这只是一个编程错误。更大的问题是未捕获的异常,这些异常将在连接仍然打开且事务未回滚时抛出给客户端。
    • @djmj:Beryllium 提出的解决方案看起来简洁优雅,即使每个连接都需要一个匿名内部类。这里的美妙之处在于它允许您以更确定的方式管理资源和处理异常。我错过了什么吗?
    • @scottb 不错的总结!我只想补充一点,您在使用站点需要更少的代码行:嵌套异常处理会使代码混乱,否则在每个使用站点。
    • @djmj:我已经更新了答案。如果 Java 8 可用,函数式风格可能看起来不那么奇怪,因为语法更好,而且它可能会在所有地方使用。
    • 我看到了这种模式对于最基本的数据访问层的好处,以防连接或其他一些参数是唯一必要的参数。但是一旦业务逻辑发生在需要几个其他参数或访问其他成员和方法的方法中,输入开销就会很大。我无法为每个可能的用例创建接口。我在一个简化的用例上更新了我的答案。我想我会看看 CMT。我希望在任何 ejb 方法返回之前调用一个带注释的方法(如@PreDestroy)。
    猜你喜欢
    • 1970-01-01
    • 2020-09-24
    • 2015-10-03
    • 1970-01-01
    • 2022-07-22
    • 2012-09-28
    • 2019-01-21
    • 2015-02-11
    • 2011-01-29
    相关资源
    最近更新 更多