Fetching Oracle 12c Implicit Cursors With JDBC and jOOQ
Having covered SQL batches, let's move onto Oracle 12c's implicit cursors and how to fetch them using jOOQ/Java while not having to worry about low-level details.
Join the DZone community and get the full member experience.
Join For FreeEarlier this week, I’ve blogged about how to execute SQL batches with JDBC and jOOQ. This was useful for the MySQL, SQL Server, and Sybase users among you.
Today, we’ll discuss a slightly more difficult task, how to fetch Oracle 12c implicit cursors – which are essentially the same thing.
What’s an Implicit Cursor?
Oracle 12c added new procedures to their dynamic SQL API DBMS_SQL. Just run the following query in SQL Developer to see the results:
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;
The anonymous PL/SQL block contains two cursors that are opened and returned to whoever calls this block using DBMS_SQL.RETURN_RESULT. This is kind of magic, as we’re calling a procedure, passing a cursor to it, and somehow, this has a side effect on the client of this program after the program ends.
Not only can you do this in anonymous PL/SQL blocks, you can nest these calls in any procedure, of course. So, in other words, from Oracle 12c onwards, you don’t know for sure if you call a procedure if there will be more results than what you can see. For instance:
BEGIN
any_procedure();
END;
The above call might just as well yield some implicit cursors. You can’t know for sure.
How to Discover Implicit Cursors With JDBC
With JDBC, if you don’t know for sure what your query will yield as a result, you use the Statement.execute(String)
, or the PreparedStatement.execute()
method to find out. As mentioned in the previous post, this is what you would do:
try (PreparedStatement s = c.prepareStatement(sql)) {
fetchLoop:
for (int i = 0, updateCount = 0;; i++) {
boolean result = (i == 0)
? s.execute()
: s.getMoreResults();
if (result)
try (ResultSet rs = s.getResultSet()) {
System.out.println("\nResult:");
while (rs.next())
System.out.println(" " + rs.getInt(1));
}
else if ((updateCount = s.getUpdateCount()) != -1)
System.out.println("\nUpdate Count: " + updateCount);
else
break fetchLoop;
}
}
Unfortunately, that won’t work on Oracle, as Oracle’s JDBC driver doesn’t implement the JDBC spec correctly. I’ve documented this flaw in length on this Stack Overflow question here.
Using ojdbc, the following “improved” loop needs to be written:
try (PreparedStatement s = cn.prepareStatement(sql)) {
Boolean result = s.execute();
fetchLoop:
for (int i = 0;; i++) {
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) {
if (e.getErrorCode() == 17283)
continue fetchLoop;
else
throw e;
}
}
else if (s.getUpdateCount() == -1)
if (result = s.getMoreResults())
continue fetchLoop;
else
break fetchLoop;
}
}
Two elements of the above logic need more explanation:
- There’s a possibility of an ORA-17283: No resultset available error being raised when accessing the
Statement.getResultSet()
despite the previous call toStatement.execute()
yieldingtrue
. If that happens, we’ll just ignore the error and try fetching another result set - In case we’re using
PreparedStatement
, the original call toPreparedStatement.execute()
will yieldfalse
(!) and theStatement.getUpdateCount()
value is-1
, which would normally mean that we should stop. Not in this case. Let’s just try one more time to get a result set, and tah-dah, here are our implicit result sets.
Note that the algorithm now works with both static Statement
and PreparedStatement
, which (very unfortunately) behave differently when calling execute()
.
The above will now work with any SQL statement. In case you’re using the previous SQL statement returning implicit cursors:
String sql =
"\nDECLARE"
+ "\n c1 sys_refcursor;"
+ "\n c2 sys_refcursor;"
+ "\nBEGIN"
+ "\n OPEN c1 FOR SELECT 1 AS a FROM dual;"
+ "\n dbms_sql.return_result(c1);"
+ "\n OPEN c2 FOR SELECT 2 AS a FROM dual;"
+ "\n dbms_sql.return_result(c2);"
+ "\nEND;";
… you will now be able to fetch all the results:
true
true
Fetching result 1
true
Fetching result 2
false
How to Get Those Cursors With jOOQ?
With jOOQ 3.10 (as always), you don’t need to worry about those low-level JDBC details. Just call the following code:
System.out.println(
DSL.using(cn).fetchMany(sql)
);
And you’ll get a convenient, object-oriented representation of your multiple result sets in the form of an org.jooq.Results
:
Result set:
+----+
| A|
+----+
| 1|
+----+
Result set:
+----+
| A|
+----+
| 2|
+----+
Even better, when you use a code generator to return multiple implicit cursors like this in a stored procedure, just call the generated stored procedure object like this, to get all the cursors automatically:
MyProcedure p = newMyProcedure();
p.setParameter1(x);
p.setParameter2(y);
p.execute(configuration);
Results results = p.getResults();
for(Result<?> result : results)
for(Record record : result)
System.out.println(record);
Done!
Published at DZone with permission of Lukas Eder. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments