当前位置: 首页 > 知识库问答 >
问题:

尝试/尝试资源和连接、语句和结果集关闭

徐隐水
2023-03-14

我最近和我的教授讨论了如何处理基本的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)教授一直要求我明确关闭结果集(即使声明文档明确表明它将关闭结果集),她说孙建议这样做。有什么理由这样做吗?

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

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)这是一个非常大且冗长的问题(如果有更多的语句,情况会变得更糟),有没有更简短或更优雅的方法来做到这一点而不使用try-with资源?

最后这是我最喜欢的代码

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) 这个代码正确吗?我认为我的教授不喜欢这种方式,因为结果集没有明确的结束,但她告诉我,只要在文档中清楚地表明所有结果都已结束,她就可以接受。你能用一个类似的例子给出官方文档的链接吗,或者根据文档中显示的代码没有问题吗?

共有3个答案

施玉宸
2023-03-14

我发现这是处理JDBC等资源的最佳解决方案。该方法通过利用最终变量,仅在需要时声明和分配这些变量,提供了一个不可变的函数,非常节省CPU,并保证在所有情况下,无论异常状态如何,分配和打开的所有资源都是关闭的。如果不仔细实施以解决所有场景,您使用的技术会留下漏洞,可能导致资源泄漏。这种技术不允许资源泄漏,前提是始终遵循以下模式:1)分配资源2)尝试3)使用资源4)最后关闭资源

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

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

            s1.executeUpdate();

         } finally {
            try { s1.close(); } catch (SQLException e) {}
         }

         final PreparedStatement s2 = con.prepareStatement(selectSqlQuery);
         try {

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

            final ResultSet rs = s2.executeQuery();
            try {

               // Do something with rs

            } finally {
               try { rs.close(); } catch (SQLException e) {}
            }
         } finally {
            try { s2.close(); } catch (SQLException e) {}
         }
      } finally {
         try { con.close(); } catch (SQLException e) {}
      }
   } catch (SQLException e) {
      throw new MyException(e);
   }
}

使用Java 7,您可以利用新的try-With资源来进一步简化这一过程:新的try-With资源遵循上述逻辑流,因为它将确保包含在分配的With-resources块中的所有资源都被关闭。with resources块中抛出的任何异常都将被抛出,但那些分配的资源仍将被关闭。此代码非常简化,如下所示:

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

      s1.executeUpdate();

         // Do something with rs

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

[编辑]:将rs赋值移动到资源块中以显示最简单的实现。在实践中,这个简单的解决方案并不真正有效,因为这效率不高。应该重用连接,因为建立连接是一项非常昂贵的操作。此外,这个简单的示例没有将查询参数分配给准备好的语句。应该注意处理这些场景,因为资源块应该只包含赋值语句。为了描述这一点,我还添加了另一个示例

   public void doQueries() throws MyException {

      final String updateSqlQuery = "select @@servername";
      final String selecSqlQuery  = "select * from mytable where col1 = ? and col2 > ?";
      final Object[] queryParams  = {"somevalue", 1};

      try (final Connection con = DriverManager.getConnection(dataSource);
         final PreparedStatement s1 = newPreparedStatement(con, updateSqlQuery);
         final PreparedStatement s2 = newPreparedStatement(con, selectSqlQuery, queryParams);
         final ResultSet rs = s2.executeQuery()) {

         s1.executeUpdate();

         while (!rs.next()) {
            // do something with the db record.
         }
      } catch (SQLException e) {
         throw new MyException(e);
      }
   }

   private static PreparedStatement newPreparedStatement(Connection con, String sql, Object... args) throws SQLException
   {
      final PreparedStatement stmt = con.prepareStatement(sql);
      for (int i = 0; i < args.length; i++)
         stmt.setObject(i, args[i]);
      return stmt;
   }
冯新知
2023-03-14

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

按顺序关闭确实会让您面临抛出异常并导致跳过一些关闭的问题。你担心这一点是对的。

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

此外,如果您反对这种冗长的做法,那么答案是将代码隐藏在使用策略、结果集映射器、预处理语句设置器等的可重用实用程序中。当然,这在以前都做过;您将踏上重塑Spring JDBC的道路。

说到这里:Spring JDBC显式关闭所有内容(可能是因为它需要使用尽可能多的驱动程序,并且不想由于某些驱动程序表现不佳而导致问题)。

东门焕
2023-03-14
  • 理论上,结束语句将结束结果集

您的代码都没有完全使用try with资源。在try-with-resources语法中,在括号中,大括号之前声明并实例化连接、预处理语句和结果集。请参阅Oracle的教程。

虽然您的结果集在上一个代码示例中没有显式关闭,但当其语句关闭时,应该间接关闭它。但正如下面所讨论的,它可能不会关闭,因为JDBC驱动程序有故障。

任何实现自动关闭的对象都将自动调用其关闭方法。所以不需要那些最后的子句。

对于读到这篇文章的人文专业学生来说,是的,Java团队把“closable”拼错了。

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

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

请参阅Oracle教程“试用资源”语句。

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

引用ResultSetJavaDoc:

当生成ResultSet对象的语句对象被关闭、重新执行或用于从一系列多个结果中检索下一个结果时,ResultSet对象会自动关闭。

正如您的老师所建议的那样,一些JDBC驱动程序中存在严重缺陷,没有达到JDBC规范的promise,即在关闭其语句或PreparedStatement时关闭结果集。许多程序员习惯于显式关闭每个结果集。

现在使用资源尝试语法,这种额外的职责变得更容易了。在实际工作中,你可能会在所有的AutoCloseable对象周围有一个其他的尝试,不管怎样,比如ResultSet。所以我自己的观点是:为什么不把它变成一个资源尝试的其他对象呢?没有坏处,让你的代码更能自我记录你的意图,如果你的代码遇到那些有缺陷的JDBC驱动程序之一,它可能会有所帮助。唯一的成本是一对括号,假设你无论如何都有一个try-cat-etc。

正如Oracle教程中所述,多个同时声明的可自动关闭对象将按相反顺序关闭,正如我们所希望的那样。

提示:try with resources语法允许在最后声明的资源项上使用可选分号。我把分号作为一种习惯,因为分号对我来说很好读,是一致的,并且便于剪切和粘贴编辑。我将其包括在您的“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.
    }
}

我认为对于这种工作,有一种更优雅的语法可能会在未来的编程语言中发明。但现在,我们已经尝试了资源,我确实很高兴地使用它。虽然try-with-resources并不完美,但它比旧语法有很大的改进。

顺便提一下,Oracle建议使用数据源实现来获取连接,而不是代码中的DriverManager方法。在整个代码中使用数据源可以更容易地切换驱动程序或切换到连接池。查看您的JDBC驱动程序是否提供了数据源的实现。

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

 类似资料:
  • 这是一种向可观察的客户列表中添加新行星的方法。 我想知道我是否正确使用资源尝试,以及自动关闭是否工作。 我的问题是,这部分需要被封闭在一个try-catch块中,还是自动关闭。

  • 为什么我们需要关闭最终区块中的资源?在hibernate中,谁负责关闭Resources结果集、语句和连接?

  • 问题内容: 我一直在看代码,并且看到了尝试资源的机会。我以前使用过标准的try-catch语句,看起来它们在做同样的事情。所以我的问题是“ 尝试使用资源”与“尝试捕获 ”之间的区别是什么,哪个更好。 这是尝试使用资源: 问题答案: 尝试使用资源的重点是确保可靠地关闭资源。 当你不使用try-with-resources时,存在一个潜在的陷阱,称为异常屏蔽。当try块中的代码引发异常,而finall

  • 不管错误情况如何,使用资源尝试是否总是关闭资源?我的意思是考虑以下代码: 会一直关闭吗?我读过Oracle文档,其中说: 无论try语句是正常完成还是突然完成,它都将关闭 因此无论程序正常运行还是抛出异常,它都将起作用。但是,类似或崩溃的情况怎么办?我知道这些条件对块不起作用。那么,使用资源尝试失败的条件是否存在? 这只是我请求的好奇心,谁能说明这一点吗?

  • 我知道Java中的安全模式是在finally块中按顺序关闭结果集、语句和连接。 若您关闭连接,然后尝试关闭语句(不引发异常)。但若您试图从语句中调用任何方法,则会引发异常。 我想知道关闭连接是否会自动关闭所有由该连接创建的语句对象? 更新:我正在使用DatabaseProductVersion:Oracle Database 11g Release 11.1.0.0.0驱动程序名称:Oracle

  • 我知道,如果资源已实现自动关闭,您通过尝试传递的资源将自动关闭。到现在为止,一直都还不错。但是,当我有几个我想要自动关闭的资源时,我该怎么办呢。带插座的示例; 所以我知道套接字将被正确关闭,因为它在try中作为参数传递,但是输入和输出应该如何正确关闭呢?