试用/试用资源和连接,语句和结果集关闭tl;博士使用资源试用语法代码示例更新: Java 9

2022-09-03 04:43:53

我最近和我的教授讨论了如何处理基本的jdbc连接方案。假设我们要执行两个查询,这就是他的建议

public void doQueries() throws MyException{
    Connection con = null;
    try {
        con = DriverManager.getConnection(dataSource);
        PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
        PreparedStatement s2 = con.prepareStatement(selectSqlQuery);

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

        rs.close();
        s2.close();
        s1.close();
    } catch (SQLException e) {
        throw new MyException(e);
    } finally {
        try {
            if (con != null) {
                con.close();
            }
        } catch (SQLException e2) {
            // Can't really do anything
        }
    }
}

我不喜欢这种方法,我有两个问题:

1.A)我认为,如果在我们做“其他事情”的地方抛出任何异常,或者在行中,或者在方法结束时不会被关闭。我说的对吗?rs.close()s2.close()s1

1.B)教授一直要求我明确关闭ResultSet(即使声明文档明确表示它将关闭ResultSet),她说Sun推荐它。有什么理由这样做吗?

现在,我认为这是同一事物的正确代码:

public void doQueries() throws MyException{
    Connection con = null;
    PreparedStatement s1 = null;
    PreparedStatement s2 = null;
    try {
        con = DriverManager.getConnection(dataSource);
        s1 = con.prepareStatement(updateSqlQuery);
        s2 = con.prepareStatement(selectSqlQuery);

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

    } catch (SQLException e) {
        throw new MyException(e);
    } finally {
        try {
            if (s2 != null) {
                s2.close();
            }
        } catch (SQLException e3) {
            // Can't do nothing
        }
        try {
            if (s1 != null) {
                s1.close();
            }
        } catch (SQLException e3) {
            // Can't do nothing
        }
        try {
            if (con != null) {
                con.close();
            }
        } catch (SQLException e2) {
            // Can't do nothing
        }
    }
}

2.A) 这个代码是否正确?(是否保证在方法结束时所有内容都将关闭?

2.B)这是非常大和冗长的(如果有更多的语句,情况会变得更糟)有没有更短或更优雅的方法来做到这一点,而不使用尝试资源?

最后,这是我最喜欢的代码

public void doQueries() throws MyException{
    try (Connection con = DriverManager.getConnection(dataSource);
         PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
         PreparedStatement s2 = con.prepareStatement(selectSqlQuery))
    {

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

    } catch (SQLException e) {
        throw new MyException(e);
    }
}

3) 此代码是否正确?我认为我的教授不喜欢这种方式,因为没有明确关闭ResultSet,但她告诉我,只要在文档中很明显所有内容都已关闭,她就可以接受它。你能用类似的例子给出任何指向官方文档的链接,或者基于文档显示此代码没有问题吗?


答案 1

tl;博士

  • 从理论上讲,关闭语句会关闭结果集。
  • 在实践中,一些有故障的JDBC驱动程序实现未能做到这一点,这是出了名的。因此,你的教练的建议是,她从硬敲门学校学到了。除非您熟悉可能为您的应用程序部署的每个 JDBC 驱动程序的每个实现,否则请使用 try-with-resources 自动关闭 JDBC 工作的每个级别,如语句和结果集。

使用资源试用语法

您的代码都没有完全使用资源试用。在资源试用语法中,在大括号前声明并实例化 、 和 括号中。请参阅 Oracle 的教程ConnectionPreparedStatementResultSet

虽然在上一个代码示例中没有显式关闭,但当关闭其语句时,间接关闭它。但如下所述,由于JDBC驱动程序有故障,它可能不会关闭。ResultSet

AutoCloseable

任何实现 AutoCloseable 的此类对象都将自动调用其方法。所以不需要这些条款。closefinally

对于阅读本文的人文学科专业的学生来说,是的,Java团队拼错了“closable”。

您如何知道哪些对象是自动闭合的,哪些不是?查看他们的类文档,看看它是否将 AutoCloseable 声明为超级接口。相反,请参阅 AutoCloseable 的 JavaDoc 页面,获取所有捆绑的子接口和实现类(实际上有几十个)的列表。

例如,对于 SQL 工作,我们看到连接语句准备语句结果集行集都是可自动关闭的,但 DataSource 不是。这是有道理的,因为存储有关潜在资源(数据库连接)的数据,但本身不是资源。A永远不会“打开”,因此无需关闭。DataSourceDataSource

请参阅 Oracle 教程,试用资源声明

代码示例

您的最后一个代码示例即将接近良好状态,但应该包装在 try-with-resources 语句中以自动关闭。ResultSet

引用 ResultSet JavaDoc:

当生成 ResultSet 对象的 Statement 对象关闭、重新执行或用于从多个结果序列中检索下一个结果时,将自动关闭该对象。

正如您的老师所建议的那样,一些JDBC驱动程序中存在严重缺陷,这些驱动程序未能兑现JDBC规范的承诺,即在其关闭或关闭时关闭。因此,许多程序员习惯于显式关闭每个对象。ResultSetStatementPreparedStatementResultSet

现在,使用 try-with-resources 语法,这种额外的职责变得更加容易。在实际工作中,您可能会尝试所有对象,例如无论如何。因此,我自己的观点是:为什么不让它成为一个尝试资源+其他?不会造成伤害,使您的代码更加自我记录您的意图,如果您的代码遇到其中一个错误的JDBC驱动程序,它可能会有所帮助。唯一的成本是一对parens,假设你无论如何都会有一个尝试捕捉其他的东西。AutoCloseableResultSet

Oracle 教程中所述,一起声明的多个对象将以相反的顺序关闭,就像我们想要的那样。AutoCloseable

提示: 使用资源试用语法允许在上次声明的资源项上使用可选分号。我习惯使用分号,因为它读起来很好,很一致,并且便于剪切和粘贴编辑。我把它包括在你的行上。PreparedStatement s2

public void doQueries() throws MyException{
    // First try-with-resources.
    try ( Connection con = DriverManager.getConnection( dataSource ) ;
          PreparedStatement s1 = con.prepareStatement( updateSqlQuery ) ;
          PreparedStatement s2 = con.prepareStatement( selectSqlQuery ) ;
    ) {

        … Set parameters of PreparedStatements, etc.

        s1.executeUpdate() ;

        // Second try-with-resources, nested within first.
        try (
            ResultSet rs = s2.executeQuery() ;
        ) {
            … process ResultSet
        } catch ( SQLException e2 ) {  
            … handle exception related to ResultSet.
        }

    } catch ( SQLException e ) {  
        … handle exception related to Connection or PreparedStatements.
    }
}

我想对于这种工作,有一个更优雅的语法,可能会在未来的编程语言中发明出来。但就目前而言,我们有资源试用,我确实很高兴使用它。虽然试用资源并不完全优雅,但它是对旧语法的一大改进。

顺便说一句,Oracle建议使用DataSource实现来获取连接,而不是代码中看到的DriverManager方法。在整个代码中使用可以更轻松地切换驱动程序或切换到连接池。查看 JDBC 驱动程序是否提供 的实现。DataSourceDataSource

更新: Java 9

现在,在 Java 9 中,您可以在尝试使用资源之前初始化资源。请参阅此文章。在某些情况下,这种灵活性可能很有用。


答案 2

关于JDBC代码的有趣之处在于,您正在按照规范进行编码,而该规范并不总是清楚您的实现的合规性。有很多不同的数据库和驱动程序,有些驱动程序比其他驱动程序表现得更好。这往往会使人们在谨慎方面犯错,建议明确关闭所有内容之类的事情。您可以在此处仅关闭连接。关闭结果集只是为了安全起见,很难与之争辩。您没有在此处指示您正在使用的数据库或驱动程序,我不想对驱动程序进行硬编码,这些假设可能对某些实现无效。

按顺序关闭内容确实会让您遇到一些问题,在这些问题上,可能会引发异常并导致跳过某些关闭。你对此感到担忧是正确的。

请注意,这是一个玩具示例。大多数实际代码使用连接池,其中调用 close 方法实际上不会关闭连接,而是将连接返回到池。因此,一旦使用池,资源可能不会关闭。如果要将此代码更改为使用连接池,则必须至少返回并关闭语句。

此外,如果您反对这样做的冗长性,那么答案是将代码隐藏在可重用的实用程序中,这些实用程序使用策略,resultSet映射器,预准备的语句设置器等。当然,这一切以前都做过。你将走上重塑春季JDBC的道路。

说到这一点:Spring JDBC明确关闭了所有内容(可能是因为它需要与尽可能多的驱动程序一起工作,并且不希望由于某些驱动程序表现不佳而引起问题)。


推荐