JDBC: getMoreResults() versus rs.next() & autoGeneratedKeys

A

Arne Vajhøj

I think you should set transaction isolation level on the connection
when you create it and use the same for queries and updates.

But I still think that the abstract super class and two sub classes
for query and update will make you code be so much nicer.

I know very little about your context.

But here is a generic example to illustrate what I am talking about:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class OODBA {
public static class RowConsoleDisplay implements RowProcessor {
public void processRow(ResultSet rs) throws SQLException {
System.out.printf("%d %s\n", rs.getInt(1), rs.getString(2));
}
}
public static class RowCountConsoleDisplay implements
RowCountAndKeyProcessor {
public void processRowCount(int rc) {
System.out.printf("rc = %d\n", rc);
}
public void processKeyRow(ResultSet rs) {
// not used
}
}
public static void main(String[] args) throws
ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection con =
DriverManager.getConnection("jdbc:mysql://localhost/Test", "root", "");
DatabaseCommand q1 = new QueryCommand(con, "SELECT f1,f2 FROM
t1", new RowConsoleDisplay());
q1.execute();
DatabaseCommand u = new UpdateCommand(con, "INSERT INTO t1
VALUES(?,?)", new RowCountConsoleDisplay());
u.execute(new ParameterSetter() {
public void set(PreparedStatement pstmt) throws SQLException {
pstmt.setInt(1, 9);
pstmt.setString(2, "IIIIIIIII");
}
});
q1.execute();
DatabaseCommand q2 = new QueryCommand(con, "SELECT f1,f2 FROM
t1 WHERE f1 = ?", new RowConsoleDisplay());
q2.execute(new ParameterSetter() {
public void set(PreparedStatement pstmt) throws SQLException {
pstmt.setInt(1, 3);
};
});
q2.execute(new ParameterSetter() {
public void set(PreparedStatement pstmt) throws SQLException {
pstmt.setInt(1, 5);
};
});
con.close();
}
}

interface ParameterSetter {
public void set(PreparedStatement pstmt) throws SQLException;
}

interface RowProcessor {
public void processRow(ResultSet rs) throws SQLException;
}

interface RowCountAndKeyProcessor {
public void processRowCount(int rc);
public void processKeyRow(ResultSet rs);
}

abstract class DatabaseCommand {
private Connection con;
private String sqlstr;
public DatabaseCommand(Connection con, String sqlstr) {
this.con = con;
this.sqlstr = sqlstr;
}
public void execute() throws SQLException {
execute(new ParameterSetter() {
public void set(PreparedStatement pstmt) {
}
});
}
public void execute(ParameterSetter params) throws SQLException {
PreparedStatement pstmt = getPreparedStatement(con, sqlstr);
params.set(pstmt);
process(pstmt);
pstmt.close();
}
public abstract PreparedStatement getPreparedStatement(Connection
con, String sqlstr) throws SQLException;
public abstract void process(PreparedStatement pstmt) throws
SQLException;
}

class QueryCommand extends DatabaseCommand {
private int rstype;
private int rsconcur;
private RowProcessor rowproc;
public QueryCommand(Connection con, String sqlstr, RowProcessor
rowproc) {
this(con, sqlstr, ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY, rowproc);
}
public QueryCommand(Connection con, String sqlstr, int rstype, int
rsconcur, RowProcessor rowproc) {
super(con, sqlstr);
this.rstype = rstype;
this.rsconcur = rsconcur;
this.rowproc = rowproc;
}
public PreparedStatement getPreparedStatement(Connection con,
String sqlstr) throws SQLException {
return con.prepareStatement(sqlstr, rstype, rsconcur);
}
public void process(PreparedStatement pstmt) throws SQLException {
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
rowproc.processRow(rs);
}
rs.close();
}
}

class UpdateCommand extends DatabaseCommand {
private boolean genkey;
private RowCountAndKeyProcessor rcakproc;
public UpdateCommand(Connection con, String sqlstr,
RowCountAndKeyProcessor rcproc) {
this(con, sqlstr, false, rcproc);
}
public UpdateCommand(Connection con, String sqlstr, boolean genkey,
RowCountAndKeyProcessor rcakproc) {
super(con, sqlstr);
this.genkey = genkey;
this.rcakproc = rcakproc;
}
public PreparedStatement getPreparedStatement(Connection con,
String sqlstr) throws SQLException {
return con.prepareStatement(sqlstr, genkey ?
PreparedStatement.RETURN_GENERATED_KEYS :
PreparedStatement.NO_GENERATED_KEYS);
}
public void process(PreparedStatement pstmt) throws SQLException {
int rc = pstmt.executeUpdate();
rcakproc.processRowCount(rc);
if(genkey) {
ResultSet rs = pstmt.getGeneratedKeys();
while(rs.next()) {
rcakproc.processKeyRow(rs);
}
}
}
}

Arne
 
A

Andreas Leitgeb

Arne Vajhøj said:
I know very little about your context.
But here is a generic example to illustrate what I am talking about:

Well, the context is somewhat more complex, and I just simply
won't get that piece of information about the kind of statement
from my caller before the time of me calling prepareStatement(...).
"Not getting the information" implies, that the caller also
won't instantiate different classes of my package beforehand,
nor will call different methods of my classes beforehand.

ResultSets, updateCounts and generated keys are supposed to be kept
available for later request (through getters), except for the general
success feedback (as in: exception thrown or not) which is "delivered"
immediately, so I also can't just defer execution until the first
getter gets called.

That may all sound a bit like "mission impossible" now...
Eventually, peeking into the sql string might be my only way out,

It's nice of the JDBC to offer a general execute() suitable for
both modification statements and queries, but unfortunately they
didn't follow that concept to its end.
 
A

Andreas Leitgeb

Arne Vajhøj said:
I think you should set transaction isolation level on the connection
when you create it and use the same for queries and updates.

I seem to have missed that line in my previous answer.

I saw in the docs, that one could set some (only two of three)
of the query-specific options as defaults in the connection.
I was a bit disappointed that one couldn't do that for the
generatedkeys option nor for the resultType (scrolling or
forward-only) and so I dismissd it...
I'll re-think that path. Maybe it's already enough for my needs.

Thanks!
 
A

Andreas Leitgeb

Arne Vajhøj said:
On 12/21/2013 1:59 PM, Arved Sandstrom wrote:
On 12/20/2013 08:08 PM, Arne Vajhøj wrote:
On 12/20/2013 9:07 AM, Andreas Leitgeb wrote:
[...] or is there really a concept of multiple ResultSets,
each of which has its own independent set of rows?
Yes.
Some databases support stored procedures returning multiple
result sets.
This might just be me, but I dislike the entire idea. I am aware of the
possibility, I've never used it. To me a stored proc (SP) should adhere
to the same principles as functions or procedures - you try to simplify
your components (and your application) by not having them do multiple
things (side-effects, overly complex return values etc).
I can follow you.
But I have seen SP's return 20+ result sets.
I guess that the argument is to save round trips.
No doubt. I've never been compelled to use multiple result sets myself.
I haven't not once encountered a project where anyone felt the need to
use multiple result sets. So I actually Googled to find out what the
rationale is.
The other is stored procs returning multiple result sets. I had no
success finding any article that provided motivation for the concept:
the authors of a few articles did mention that they wished not to
explain *why*, but just *how*...which is faint praise in my books. :)
This particular concept seems to me to be much more "we can do this, so
we will".
http://msdn.microsoft.com/en-us/data/jj691402.aspx
http://stackoverflow.com/questions/2336362/execute-multiple-sql-commands-in-one-round-trip
do mention the round trip aspect.

Thanks for this insightful thread about pros (saving round-trips)
and cons (not "KISS") of the multiple-resultsets concept.

Fortunately, it's something that I will likely not have to deal with
within my current task. :)
 
A

Arne Vajhøj

Well, the context is somewhat more complex, and I just simply
won't get that piece of information about the kind of statement
from my caller before the time of me calling prepareStatement(...).
"Not getting the information" implies, that the caller also
won't instantiate different classes of my package beforehand,
nor will call different methods of my classes beforehand.

ResultSets, updateCounts and generated keys are supposed to be kept
available for later request (through getters), except for the general
success feedback (as in: exception thrown or not) which is "delivered"
immediately, so I also can't just defer execution until the first
getter gets called.

That may all sound a bit like "mission impossible" now...
Eventually, peeking into the sql string might be my only way out,

It's nice of the JDBC to offer a general execute() suitable for
both modification statements and queries, but unfortunately they
didn't follow that concept to its end.

I still believe that it is the code responsible for the SQL that
should know all the details about (whether keys will be generated
for an insert or whether the result set need to be scrolled
backwards etc.).

But You seem to have been given the context and need to find
a practical solution.

And as a practical solution, then peeking into the SQL string
is not bad.

Arne
 
A

Andreas Leitgeb

Sorry for lateness of answer...

lipska the kat said:
IIRC multiple ResultSet can be returned from a stored procedure, in
this case you would need to iterate over the ResultSet using the
method getResultSet() to get the first results and getMoreResults() to
check if there are more results. If there are, calling getResultSet()
again would return the next results. I've never seen a single sql
statement that would return multiple result sets but this doesn't mean
it's not possible (obviously)


Ok, thanks. I haven't yet come across a stored proc returning
more than one "table"-ful of data, so I learnt something new here.
You would do this in Java, surely. Use some abstract factory type
mechanism to generate a suitable class depending on the type of query.

This "depending on the type of query" is the very problem.
 
A

Andreas Leitgeb

lipska the kat said:
Well sooner or later something has to know what you want doesn't it?
You have to make a decision sometime don't you?

The workflow is like this: a method is called with an "arbitrary string"

I then create a PreparedStatement for this string, and execute() it,
and use execute()'s boolean return value to either obtain the ResultSet
or updateCount/generated keys, and assemble the first row or the
updatecount/keys into a response. (Think of an interactive utility a
bit like sqlplus.)

Eventually, however, next command may be to advance to another row and
for that case I need to have checked beforehand, that the implicit
cursor possibly created from that previous execute() would have certain
properties w.r.t *isolation*, *scrollability* & *holdability*.

Those cursor-related flags have been previously set up for the session,
but only two of the three flags can be set on the java.sql.Connection
instance. Scrollability can not be pre-set on Connection level.

This means, that if session options are set for scrollable cursors,
then I just cannot report generated keys (because I need to use the
one prepareStatement-overload that allow me to specify scrollability
and does not allow me to also possibly request generated keys).

Seems like there's no other way than live with it, anyway, that if
generated keys are needed for some statement, then the scroll-flag
needs to be (temporarily) turned off.
 
A

Arne Vajhøj

Sorry for lateness of answer...

lipska the kat said:
IIRC multiple ResultSet can be returned from a stored procedure, in
this case you would need to iterate over the ResultSet using the
method getResultSet() to get the first results and getMoreResults() to
check if there are more results. If there are, calling getResultSet()
again would return the next results. I've never seen a single sql
statement that would return multiple result sets but this doesn't mean
it's not possible (obviously)


Ok, thanks. I haven't yet come across a stored proc returning
more than one "table"-ful of data, so I learnt something new here.


It is rather common in the Sybase ASE/Microsoft SQLServer world.

And supported in some other.

And not supported in some (Oracle among those I believe).

Arne
 
A

Andreas Leitgeb

Arne Vajhøj said:
Sorry for lateness of answer...
lipska the kat said:
IIRC multiple ResultSet can be returned from a stored procedure, in
this case you would need to iterate over the ResultSet using the
method getResultSet() to get the first results and getMoreResults() to
check if there are more results. If there are, calling getResultSet()
again would return the next results. I've never seen a single sql
statement that would return multiple result sets but this doesn't mean
it's not possible (obviously)

Ok, thanks. I haven't yet come across a stored proc returning
more than one "table"-ful of data, so I learnt something new here.

It is rather common in the Sybase ASE/Microsoft SQLServer world.
And supported in some other.
And not supported in some (Oracle among those I believe).


That explains, why I haven't come across it yet.
 
F

firstsql

I have to write code to execute runtime-specified SQL-statements,

and there are two things that I wasn't able to understand from reading

the javadocs:



1.)

When would one call .getMoreResults() on a statement instead of just

another rs.next() on the first one (obtained from stmnt.getResultSet())?

Are these equivalent, or is there really a concept of multiple ResultSets,

each of which has its own independent set of rows?

Yes, there is the concept of multiple independent ResultSets.

Seeing the comments elsewhere in this thread, I was kind of surprised that stored procedures returning multiple ResultSets was somewhat obscure and that Oracle doesn't support them.

I was also under the apparently mistaken impression that passing multiple statements (optionally separated by semi-colons or whatever) to a single executeXXX(), or whatever, was reasonably well supported. This case allows notjust multiple queries but also a mixture of statements: query, update, control and even DDL, returning multiple 'results', instead of just multiple ResultSets.

Our ORDBMS allows all of the above, plus the capability for a stored procedure to return a JDBC statement, potentially containing mixed results. I guess we're just the best ;^)
2.)

As I don't know the statement beforehand, I can't decide at coding

time, if it might be an "insert" eventually returning a serial key,

and thusly requiring the argument RETURN_GENERATED_KEYS), or a select

that will need cursor-like treatment such as passing resultSetType,

resultSetConcurrency and resultSetHoldability flags.

How would I tell the JDBC engine, that if it is a select then it

should pass certain flags, and if it is an insert, then I would be

interested in the generated keys? prepareStatement() doesn't seem to

have an overload to accept both variants.

Just another weakness in JDBC, choosing brevity over capability, and probably catering to their 'masters'. This really should be up to the drivers themselves.

As to knowing before whether the SQL command can return generated keys, thejavadocs say that the RETURN_GENERATED_KEYS parameter IS ignored if "an SQL statement [is not] able to return auto-generated keys". Unfortunately, some drivers may violate this ... YMMV. So given a compliant driver, you could specify RETURN_GENERATED_KEYS for any type of SQL statement. This could also work for multiple SQL command in a single SQL statement.
Finally, why isn't RETURN_GENERATED_KEYS the default, anyway?

It could be an optimization; the server has to do a bit of work to service RETURN_GENERATED_KEYS and variants. Of course, a driver could send a separate special request to the server when it saw a getGeneratedKeys(). Then again, it could be more 'catering'.
 
A

Arne Vajhøj

Yes, there is the concept of multiple independent ResultSets.

Seeing the comments elsewhere in this thread, I was kind of
surprisedthat stored procedures returning multiple ResultSets was
somewhat obscure and that Oracle doesn't support them.

I was also under the apparently mistaken impression that passing
multiple statements (optionally separated by semi-colons or whatever)
to a single executeXXX(), or whatever, was reasonably well supported.
This case allows not just multiple queries but also a mixture of
statements: query, update, control and even DDL, returning multiple
'results', instead of just multiple ResultSets.

Allowing multiple SQL statements in a single execute can make
SQL injection much worse.

I would consider it a security flaw.

And it is not widely supported.

A quick test shows that the following does not allow it:
- MySQL by default
- Oracle
- DB2
- H2
- Derby/JavaDB
and only the following allow it:
- SQLServer
- MySQL with allowMultiQueries=true

Arne
 
M

Marcel Müller

Allowing multiple SQL statements in a single execute can make
SQL injection much worse.
I would consider it a security flaw.

I do not agree.

Code that passes user input as part of SQL code to a database is broken
by design, not the database driver that can execute more than one statement.

And on the other side there could be good reasons execute multiple
statements as a block. Preferably performance issues.


Marcel
 
A

Arne Vajhøj

I do not agree.

Code that passes user input as part of SQL code to a database is broken
by design, not the database driver that can execute more than one
statement.

If SQL injection happens then there is certainly a bug in
the application code.

But good software design does not assume that all other code
is implemented correctly.

So you:
* configure mod_security in Apache to check for injection attempts
* validate input in PL
* store data in strongly typed objects in BLL
* use prepared statements in DAL
* use a JDBC driver that does not allow multiple statements to be
executed in one call
* configure database security of that the connections has minimal priviliges

To defend against SQL injections in depth.
And on the other side there could be good reasons execute multiple
statements as a block. Preferably performance issues.

For INSERT/UPDATE/DELETE you can do it safely in JDBC via batch.

If the same functionality was needed for SELECT then add SELECT support
in batch would be safer than allowing multiple statements in one call.

Arne
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,990
Messages
2,570,211
Members
46,796
Latest member
SteveBreed

Latest Threads

Top