Sum Ergo Mea Culpa Est

Traversing JDBC Results

Suppose you want to execute a Statement that someone hands you.

(If it's a plain Statement, then at a minimum you'll also need the SQL to execute, along with maybe some other metadata.)

Next, you might think you need to know whether it's a query (does it return a ResultSet, you might think?) or some hazy kind of non-query update thing (maybe it changes some rows?). You don't. Any supposedly "generic" JDBC framework that forces you to declare this sort of thing up front doesn't understand how JDBC works.

Next, you are probably thinking that when you execute a statement you get one thing back that represents the return value. (You don't.) Again, any supposedly "generic" JDBC framework that forces you to rely on this sort of thing up front doesn't understand how JDBC works.

To execute any generic Statement is surprisingly complicated.

First, assuming you have the SQL, you call the execute method. Not executeQuery, not executeBatch, not executeUpdate, and so on.

The execute method will return a boolean. Does that mean "the execution succeeded"? No.

Instead: if it is true, then the execution succeeded, and, moreover, the first of sometimes many results is a ResultSet.

If it is false, then the execution succeeded, but the first of sometimes many results is an update count. (If ever the update count is less than zero, then there are no further results. If in addition the initial execution yielded false, then there were exactly zero "real" results of any kind, whatever that might mean, but don't forget to check for OUT parameter values.)

If the first of possibly many results is a ResultSet, then you can call Statement#getResultSet() to get that very ResultSet. In 99.99% of cases, it will be the only one. Very sadly, the entire Java enterprise world happens to run, if ever successfully, only by pure chance based entirely on this completely incorrect assumption.

(Tiny but important tangent: some of this is due to the fact that the wildly popular H2 database, used at test time to validate various JDBC assumptions, silently ignores situations where multiple results are called for. Oops.)

If instead the first of possibly many results is an update count, then you can call Statement#getUpdateCount() to get the update count.

(If the statement execution happened to return an update count greater than Integer#MAX_VALUE, then really you should have called Statement#getLargeUpdateCount() instead. But your driver may not support that, so it's best to call Statement#getLargeUpdateCount() first, see if you get a SQLFeatureNotSupportedException, and then call Statement#getUpdateCount().)

To move to the next result (yes, any statement, including simple queries, can contractually return multiple results, and no, JDBC does not represent "results" as an object, unless you count Statement itself), you call Statement#getMoreResults() (or possibly Statement#getMoreResults(int), and now you're into managing multiple "live" ResultSet instances territory (potentially)). This has the same general contract as execute: if it returns true, you're in ResultSet territory, and if it returns false, you're in update count territory.

So you're done, right? Not so fast. If the particular kind of Statement you executed was a CallableStatement, and if, prior to execution, it had registered either INOUT or OUT parameters, then the values assigned to those parameters will be semantically available once, and only once, all other results from the statement execution are available.

(I've also elided over various details regarding ResultSets, such as their holdability, whether they are kept alive and so on.)

All of this is true no matter what statement you execute. This is simply how you must process the results of execution. I've not once seen this implemented all-the-way properly in 30 years of enterprise Java programming. This is surprising, because whatever you think of this protocol, it is squarely and thoroughly laid out in the documentation.

It is a little surprising to me that a single traversal-related class/object representing all of this is not available as the result of a Statement execution. You need one in order to return a single Thing™ that represents the outcome of the execution of a JDBC statement. None is forthcoming, in JDBC, anyway. Stay tuned.