Some databases (in particular MySQL and T-SQL databases like SQL Server and Sybase) support a very nice feature: They allow for running a “batch” of statements in a single statement. For instance, in SQL Server, you can do something like this:
-- Statement #1
DECLARE @table AS TABLE (id INT);
-- Statement #2
SELECT * FROM @table;
-- Statement #3
INSERT INTO @table VALUES (1),(2),(3);
-- Statement #4
SELECT * FROM @table;
This is a batch of 4 statements, and it can be executed as a single statement both with JDBC and with jOOQ. Let’s see how:
Executing a batch with JDBC
Unfortunately, the term “batch” has several meanings, and in this case, I don’t mean the JDBC
Statement.addBatch()
method, which is actually a bit clumsy as it doesn’t allow for fetching mixed update counts and result sets.
Instead, what I’ll be doing is this:
String sql =
"\n -- Statement #1 "
+ "\n DECLARE @table AS TABLE (id INT); "
+ "\n "
+ "\n -- Statement #2 "
+ "\n SELECT * FROM @table; "
+ "\n "
+ "\n -- Statement #3 "
+ "\n INSERT INTO @table VALUES (1),(2),(3); "
+ "\n "
+ "\n -- Statement #4 "
+ "\n SELECT * FROM @table; ";
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;
}
}
The output of the above program being:
Result:
Update Count: 3
Result:
1
2
3
The above API usage is a somewhat “hidden” – or at least not every day usage of the JDBC API. Mostly, you’ll be using
Statement.executeQuery()
when you’re expecting a
ResultSet
, or
Statement.executeUpdate()
otherwise.
But in our case, we don’t really know what’s happening. We’re going to discover the result types on the fly, when executing the statement. Here are the main JDBC API features that we’re using, along with an explanation:
Statement.execute()
: This method should be used if we don’t know the result type. The method returns a boolean
, which is true
when the first statement in the batch produced a ResultSet
and false
otherwise.
Statement.getMoreResults()
: This method returns the same kind of boolean
value as the previous Statement.execute()
method, but it does so for the next statement in the batch (i.e. for every statement except the first).
- If the current result is a
ResultSet
(the boolean
was true
), then we’ll obtain that ResultSet
through Statement.getResultSet()
(we can obviously no longer call the usual Statement.executeQuery()
to obtain the ResultSet
).
- If the current result is not a
ResultSet
(the boolean
was true
), then we’ll check the update count value through Statement.getUpdateCount()
.
- If the update count is
-1
, then we’ve reached the end of the batch.
What a nice state machine!
The nice thing about this is that a batch may be completely nondeterministic. E.g. there may be triggers, T-SQL blocks (e.g. an
IF
statement), stored procedures, and many other things that contribute result sets and/or update counts. In some cases, we simply don’t know what we’ll get.
Executing a batch with jOOQ
It’s great that the JDBC API designers have thought of this exotic API usage on a rather low level. This makes JDBC extremely powerful. But who remembers the exact algorithm all the time? After all, the above minimalistic version required around 20 lines of code for something as simple as that.
Compare this to the following jOOQ API usage:
System.out.println(
DSL.using(c).fetchMany(sql)
);
The result being:
Result set:
+----+
| id|
+----+
Update count: 3
Result set:
+----+
| id|
+----+
| 1|
| 2|
| 3|
+----+
Huh! Couldn’t get much simpler than that! Let’s walk through what happens:
The
DSLContext.fetchMany()
method is intended for use when users
know there will be many result sets and/or update counts. Unlike JDBC which reuses ordinary JDBC API, jOOQ has a different API here to clearly distinguish between behaviours. The method then eagerly fetches all the results and update counts in one go (
lazy fetching is on the roadmap with issue #4503).
The resulting type is
org.jooq.Results
, a type that extends
List<Result>
, which allows for iterating over the results only, for convenience. If a mix of results or update counts need to be consumed, the
Results.resultsOrRows()
method can be used.
A note on warnings / errors
Note that if your batch raises errors, then the above JDBC algorithm is incomplete.
Read more about this in this follow-up post.
Like this:
Like Loading...
This may be a nice feature but it should also be pointed out that this is a “nice” enabler for SQL injections too, which is the reason why Oracle and others don’t allow this functionality. Although this feature can come in handy for the programmer, he should also consider the security aspect of this.
It doesn’t matter if the server supports batches or not. Sure, if batches are supported, SQL injection becomes a bit easier, but if you automate exploiting a vulnerability, you’ll be able to seize the server anyway.
Hmm, in fact, this JDBC feature will also be useful in Oracle 12c, when returning multiple undeclared cursors like this, right?
jOOQ will support these in version 3.10:
https://github.com/jOOQ/jOOQ/issues/5666
It appears four statements were executed, but how results (3 of them) map to executed statements is not clear. What is the result of statement #1? Reading the code to map sequence of results suggests a result is missing
It appears that
DECLARE
“statements” aren’t really true statements in T-SQL. You won’t get any results in SQL Server Management Studio for theDECLARE
statement either.