2

目前在具有本地和远程 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);
        }
    }
4

1 回答 1

2

一般来说,这个问题是关于以简单的方式正确地进行资源管理 。这总是需要两个要素:一个简单的 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 上的匿名类之类的东西。

于 2013-09-26T13:13:12.423 回答