使用JDBC从Oracle查询中获取所有(可能是隐式)结果的正确方法是什么?

Luk*_*der 9 java oracle jdbc

从Oracle 12c开始,我们可以从客户端获取隐式游标.例如,可以在SQL Developer中运行以下PL/SQL匿名块

DECLARE
  c1 sys_refcursor;
  c2 sys_refcursor;
BEGIN
  OPEN c1 FOR SELECT 1 AS a FROM dual;
  dbms_sql.return_result(c1);
  OPEN c2 FOR SELECT 2 AS b FROM dual;
  dbms_sql.return_result(c2);
END;
Run Code Online (Sandbox Code Playgroud)

要获得以下结果:

ResultSet #1
A                                       
--------------------------------------- 
1                                       

ResultSet #2
B                                       
--------------------------------------- 
2
Run Code Online (Sandbox Code Playgroud)

这几乎像MySQL或SQL Server批处理(例如,如本文所示),所以我认为我们应该能够运行以下代码:

try (Statement s = connection.createStatement()) {
    boolean result = s.execute(sql); // Plug above SQL here

    fetchLoop:
    for (int i = 0;; i++) {
        if (i > 0) result = s.getMoreResults();
        System.out.println(result);

        if (result)
            try (ResultSet rs = s.getResultSet()) {
                System.out.println("Fetching result " + i);
                // ...
            }
        else if (s.getUpdateCount() == -1)
            break fetchLoop;
    }
}
Run Code Online (Sandbox Code Playgroud)

这导致ojdbc6版本12.1.0.1.0出错:

true
java.sql.SQLException: Oracle.main
上的oracle.jdbc.driver.OracleStatementWrapper.getResultSet(OracleStatementWrapper.java:388)
中的oracle.jdbc.driver.OracleStatement.getResultSet(OracleStatement.java:3369)
中没有可用的结果集(Oracle的.java:46)

这似乎违反了该Statement.execute()方法,其Javadoc指出:

返回:
如果第一个结果是ResultSet对象,则返回 true; 如果是更新计数或没有结果,则返回false

所以,一旦Statement.execute()收益率true,我Statement.getResultSet()应该返回一个结果集.解决方法是:

try (Statement s = connection.createStatement()) {
    s.execute(sql);                          // WORKAROUND: Ignore this result

    fetchLoop:
    for (int i = 0;; i++) {
        boolean result = s.getMoreResults(); // WORKAROUND: Take the result from here
        System.out.println(result);

        if (result)
            try (ResultSet rs = s.getResultSet()) {
                System.out.println("Fetching result " + i);
                // ...
            }
        else if (s.getUpdateCount() == -1)
            break fetchLoop;
    }
}
Run Code Online (Sandbox Code Playgroud)

结果现在是:

true
获取结果0
true
获取结果1
false

但这似乎是错误的API使用.根据我对JDBC规范的理解,这个"改进的"循环现在将跳过第一个结果集.

更糟糕的是,准备好的陈述表现不同.以下代码:

try (PreparedStatement s = cn.prepareStatement(sql)) {
    boolean result = s.execute();

    fetchLoop:
    for (int i = 0;; i++) {
        if (i > 0) result = s.getMoreResults();
        System.out.println(result);

        if (result)
            try (ResultSet rs = s.getResultSet()) {
                System.out.println("Fetching result " + i);
                // ...
            }
        else if (s.getUpdateCount() == -1)
            break fetchLoop;
    }
}
Run Code Online (Sandbox Code Playgroud)

不提取任何结果集但只是退出:


它再次以这种方式工作:

try (PreparedStatement s = cn.prepareStatement(sql)) {
    s.execute();

    fetchLoop:
    for (int i = 0;; i++) {
        boolean result = s.getMoreResults();
        System.out.println(result);

        if (result)
            try (ResultSet rs = s.getResultSet()) {
                System.out.println("Fetching result " + i);
                // ...
            }
        else if (s.getUpdateCount() == -1)
            break fetchLoop;
    }
}
Run Code Online (Sandbox Code Playgroud)

true
获取结果0
true
获取结果1
false

我的问题(最后)

假设我正在编写通用JDBC客户端代码,它不知道SQL字符串包含什么(它可能只是普通的查询).我想获取我可能得到的所有结果集.

  • ojdbc是否违反了JDBC规范?
  • 如果SQL字符串未知,从ojdbc获取所有结果集的正确方法是什么?

需要明确的是,上述解决方法对于普通查询来说是错误的SELECT 1 FROM dual.

Luk*_*der 2

目前(虽然这可能是也可能不是 ojdbc 中的错误),我发现这个令人讨厌的解决方法涵盖了 JDBC API 的所有用法(不知道 SQL 字符串产生什么):

/* Alternatively, use this for non-PreparedStatements:
try (Statement s = cn.createStatement()) {
    Boolean result = s.execute(sql); */
try (PreparedStatement s = cn.prepareStatement(sql)) {
    // Use good old three-valued boolean logic
    Boolean result = s.execute();

    fetchLoop:
    for (int i = 0;; i++) {

        // Check for more results if not already done in this iteration
        if (i > 0 && result == null)
            result = s.getMoreResults();
        System.out.println(result);

        if (result) {
            result = null;

            try (ResultSet rs = s.getResultSet()) {
                System.out.println("Fetching result " + i);
            }
            catch (SQLException e) {
                // Ignore ORA-17283: No resultset available
                if (e.getErrorCode() == 17283)
                    continue fetchLoop;
                else
                    throw e;
            }
        }
        else if (s.getUpdateCount() == -1)
            // Ignore -1 value if there is one more result!
            if (result = s.getMoreResults())
                continue fetchLoop;
            else
                break fetchLoop;
    }
}
Run Code Online (Sandbox Code Playgroud)