【问题标题】:JDBC Statement.closeOnCompletion() should close statement on next execution?JDBC Statement.closeOnCompletion() 应该在下次执行时关闭语句吗?
【发布时间】:2021-05-20 07:32:36
【问题描述】:

JDBC API 有以下与ResultSet 相关的注释:

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

在 JDBC 4.3 规范中:

13.1.4 关闭语句对象

关闭 Statement 对象将关闭该 Statement 对象生成的任何 ResultSet 实例并使该实例无效。

此时很清楚,关闭一个语句对象,应该关闭ResultSet

Statement的JavaDoc有这样的注释:

Statement 接口中的所有执行方法都会隐式关闭一个 如果存在打开的,则该语句的当前 ResultSet 对象。

现在,问题是Statement.closeOnCompletion() 应该如何表现?

指定当它的所有依赖时该语句将被关闭 结果集已关闭。如果执行语句没有产生 任何结果集,此方法无效。

注意:...但是,对 closeOnCompletion 的调用确实会影响后续的 执行语句,以及当前已打开的语句, 依赖,结果集。

语句应该允许重新执行?还是重新执行应该关闭第二次执行的语句?

以测试为例:

  @Test
  public void testCloseOnCompletionMultipleExecutionResultSets() throws SQLException {
    Statement statement = conn.createStatement();
    ResultSet rs1 = statement.executeQuery("SELECT 1");
    assertFalse("rs1 should be open", rs1.isClosed());

    statement.closeOnCompletion();
    // Should the second execution throw an SQLException with "Statement closed"?
    // or it should work and the statement be closed until the second rs is closed?
    ResultSet rs2 = statement.executeQuery("SELECT 2"); // fail or not?

    assertTrue("rs1 should be closed by rs2", rs1.isClosed());
    assertFalse("rs2 should be open", rs2.isClosed());
    assertFalse("statement should be open", statement.isClosed());
    rs2.close(); // Only close the statement here since is the last rs open.
    assertTrue("statement should be closed", statement.isClosed());
  }

相关讨论:https://github.com/pgjdbc/pgjdbc/pull/1905

【问题讨论】:

    标签: java jdbc


    【解决方案1】:

    您问题的答案必须是“是”。

    在得到 JDBC 专家的澄清后,我将更改我原来的回复。

    所以,closeOnCompletion 确实必须关闭语句,而不管结果集是如何关闭的,即,无论是由用户显式关闭还是在后续执行中使用该语句时隐式关闭,因此必须出现异常被扔掉。

    不过,还有一个问题有待澄清。该方法的 JavaDoc 说:

    指定当所有相关结果集都关闭时,该语句将被关闭。如果 Statement 的执行没有产生任何结果集,则此方法无效。

    第一句毫无疑问,但第二句与原意不太吻合,即禁止用该语句重新执行,无论结果集是否已生成。

    这导致我们进入另一个极端情况:如果我们调用closeOnCompletion() 语句执行(一次或多次)并且没有来自先前执行的结果集或所有结果集时会发生什么已经关闭了?规范中的注释说:

    但是,对closeOnCompletion 的调用确实会影响语句的后续执行,以及当前具有打开、相关结果集的语句。

    因此,该语句可能处于“重置”状态,就像新创建的语句一样,并且可以再执行一次;或者它必须在调用 closeOnCompletion() 时立即关闭,就像它在前一个 execute 之前或在关闭最后一个结果集之前调用一样。

    如果有其他说明,我会添加更多详细信息。

    【讨论】:

    • 结果集的隐式和显式关闭都会导致完成。完成是关于语句执行,而不是用户的意图。我不知道 JDBC 规范中支持这种解释的任何内容。关于切换的评论只是为了避免人们认为他们可以通过再次调用该方法来“取消”closeOnCompletion,就好像它在两种状态之间切换一样。
    • @MarkRotteveel 你是对的。我更改了原来的回复。
    【解决方案2】:

    调用closeOnCompletion() 表示您希望语句在关闭其最后一个结果集(它“完成”执行)后关闭,因此一旦执行完成,该语句将自行关闭。对于非结果集生成语句,它没有效果(您应该自己致电close())。对于生成结果集的语句,一旦关闭最后一个结果集,执行就完成了。主要用例是当您返回一个结果集进行处理,但不想跟踪其语句时。

    假设对同一语句的另一次执行将关闭该语句的任何打开结果集,启用 closeOnCompletion 后,将自动关闭该语句。

    发出closeOnCompletion 信号将是用户错误,但随后通过执行另一个语句继续使用该语句,因此引发SQLException 是合适的。

    顺便说一句,正文

    但是,对closeOnCompletion 的调用确实会影响后续的 执行语句,以及当前已打开的语句, 依赖,结果集。

    表示如果您调用closeOnCompletion,它将影响下一个生成结果集的执行,或者如果您当前打开了一个结果集,则影响当前执行。

    Lance Andersen(JDBC 规范负责人)和 Douglas Surber(代表 Oracle 的 JSR-221 JDBC 专家组成员)在 jdbc-spec-discuss 邮件列表上的 the discussion 回复也支持这一点,具体来说:

    总体意图是处理类似于以下内容的代码:

    ResultSet rs = foo();
    while(rs.next() {
         /*do something */
    }
    rs.close();
    
    public ResultSet foo() {
    
       Statement stmt = con.createStatement();
       stmt.closeOnCompletion();
       ResultSet rs = stmt.executeQuery(aQuery);
       return rs
    }
    

    我们确实在 2009 年在这方面花费了很多时间,并且花费了相当多的时间 就目前的措辞达成一致的时间。然而,它确实 看起来有可能进行一些额外的文字锻造。

    (兰斯·安徒生,https://mail.openjdk.java.net/pipermail/jdbc-spec-discuss/2021-February/000542.html


    我写了最初的提案。目的是处理兰斯的案子 描述。它并不打算允许多次执行 陈述。因此,虽然语言可能不像它需要的那样清晰 是,Filipe 描述的情况应该引发第二次执行 该声明。至少这是我的意图。

    (道格拉斯·苏伯,https://mail.openjdk.java.net/pipermail/jdbc-spec-discuss/2021-February/000543.html


    正如 Douglas 在讨论时指出的那样,JDBC EG 共识是该声明将被关闭。如果应用程序没有 想要这种行为,他们不应该调用 Statement::closeOnCompletion 如果他们不确定该方法是否被调用,他们可以 总是调用 Statement::isCloseOnCompletion 来做决定 以编程方式。

    如您所知,这个区域已经够乱了,其目的是 解决导致应用程序常见问题的常见用例 而不是让事情变得更复杂。

    (兰斯·安徒生,https://mail.openjdk.java.net/pipermail/jdbc-spec-discuss/2021-February/000548.html


    【讨论】:

      【解决方案3】:

      听起来这个问题是从“实现 JDBC 的人”的相当奇特的位置提出的,而不是作为用户提出的。感谢您为维护 postgres JDBC 绑定所做的努力:)

      @MarkRotteveel 在这里提供了很多背景信息。不过还有一点很重要:

      来自 javadoc:

      来自Statement.closeOnCompletion:指定当所有相关结果集都关闭时,该语句将被关闭。

      来自Statement.getResultSet:以 ResultSet 对象的形式检索当前结果。

      这些似乎是相互排斥的:一个表明单个语句可以同时激活多个结果集,另一个表明 当前结果与 ResultSet 本身的 javadoc 相结合(当生成它的 Statement 对象关闭、重新执行或用于从多个结果序列中检索下一个结果时,ResultSet 对象会自动关闭。)这表明最多只能有一个结果集。

      我猜第二种解释更正确;它不仅由文档支持,而且由方法结构支持(假设有一个 getResultSet 方法不返回结果集数组,即使没有文档也有一些“证据”表明只能有一个),并在更多地方与更多 javadoc 相结合。

      但是,有一种解释方法可以避免必须向 openjdk.org 提交有关此冲突文档的错误:JDBC 为实现者留出了空间来添加 确实 分拆的自定义方法多个结果集,全部处于活动状态,尽管这意味着 getResultSet() 应该返回什么仍然是一个谜。我很确定 postgres JDBC 驱动程序没有任何此类方法,这使其成为一个有争议的问题。

      由此得出一个简单的结论:

      如果从标记为完成时关闭并且之前已经创建过结果集的语句创建了新结果集,则:

      • 之前的结果集必须关闭。
      • 然后 [A] this 然后关闭语句,因此必须抛出 SQLException:尝试执行已关闭的语句,--或--
      • [B] 由于原始结果集的关闭是隐式的,因此该语句关闭,而新创建的结果集现在具有以下属性:“如果关闭我,则关闭陈述”。在旧的 ResultSet 上调用 close() 应该是无操作的,如果这是选择的路由,则应该 not 关闭语句:它不再是活动的结果集,因此 javadoc 很清楚 ( re-) 关闭这个对 close on completion 功能没有影响。

      文档对我来说似乎并不清楚这两个选项中的哪一个是正确的,但我认为这并不重要:要么显然是程序员想要的行为 (B),要么不可避免地出现异常,并且程序员将很快被告知这种从 close on complete 语句旋转多个结果集的场景永远不会起作用。

      实际上,这一点可能几乎完全没有实际意义:在 JDBI/JOOQ 和 try-with-resources 等框架之间,close on complete 只有一个真正有用的功能:允许代码返回 ResultSet从新创建的语句对象创建,并将其返回,将关闭它的责任转移给调用者。

      调用者可以然后这样做:

      resultSet.getStatement().execute(...);
      

      但那将是恶意的白痴:鉴于您不知道它是哪种声明,您不知道可以用它做什么(简单的 jane Statement 允许 execute(String),但 PreparedStatement 是明确的如果您尝试调用该方法,预计会立即抛出。奇怪的是,“只是抛出”是非常好的行为。

      【讨论】:

      • 您的假设是不正确的,即这是互斥的。有些语句可以产生多个结果集(例如 SQL Server 中的存储过程)。通过调用getMoreResults 移动到下一个结果集,检查其返回值,然后调用getResultSet(或getUpdateCount),直到没有更多结果。 closeOnCompletion 将在检索并关闭最后一个结果集时关闭语句(尽管更正确的是检索最后一个结果,因为完全有可能最后一个结果集后面跟着 1 个或多个更新计数)。
      • 但是只有一个“活动”结果集,并通过例如前进到下一个结果集。 getMoreResults 应该按照 JDBC javadoc 的其余部分隐式关闭前一个,不是吗?即活动的可以打开或关闭,所有非活动的都必须关闭,因为使它们不活动的东西隐含地关闭了它们。
      • 嗯,我猜如果重复调用 getMoreResults 会产生多个结果集,那么 javadoc 可以读作:如果关闭不是序列中最后一个的 ResultSet,则采购语句完全 NOT 关闭;仅适用于序列的最后一个 ResultSet?
      • 这就是为什么closeOnCompletion“当它的所有依赖结果集都关闭时。”,如果一个语句执行产生多个结果集,所有这些结果集都是它的“相关结果集”。此外,来自同一执行的先前结果集是否关闭取决于传递给getMoreResults(int) 的参数,因此有可能(但在我的经验中不常见)在一个语句对象上打开多个结果集。跨度>
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-18
      • 2011-09-23
      相关资源
      最近更新 更多