6

In my java app, it seems to use parameters in my query to the database, I need to utilize the PreparedStatement. However at the same time, I would like to use the resultset from the statement in forward/backward mode (scrollable) PreparedStatement does not seem to offer setting the scrollable mode Statement does not seem to offer parameters.

Seems like a basic question..but nothing jumping out at me (other than using Statement and constructing the SQL without parameters). Is there really no way to supply parameters to a Statement..or have a preparedstatement scrollable? Am I missing something?

            conn = Utility.getConnection();

            tmpSQL = "SELECT * FROM " + baseTable + " WHERE " + filterCriteria
                    + " ORDER BY " + sortCriteria;

//method 1

Statement stmt = conn.createStatement(
                       ResultSet.TYPE_SCROLL_INSENSITIVE,
                       ResultSet.CONCUR_UPDATABLE);

rset = stmt.executeQuery(tmpSQL);  //not using any parameters!


//method 2

            PreparedStatement pStatement = conn.prepareStatement(tmpSQL);  //not scrollable!

            if (params != null)
                for (int i = 0; i < params.size(); i++) {

                    pStatement.setString(i + 1,
                            ((Parameter) params.get(i)).getStringValue());

                }

            rset = pStatement.executeQuery();
da Bich
  • 494
  • 1
  • 4
  • 13
  • Resultset.updateString(index, value) and ResultSet,updateRow() may help – BigMike Feb 23 '16 at 15:27
  • Try [this](https://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html#prepareStatement(java.lang.String,%20int,%20int)) method instead – lance-java Feb 23 '16 at 15:28
  • maybe i should expand on the reason i need scrollable... i just wanted to get a record count of the result set before doing my other operations. maybe there is a better way to get that without needing scrollable (though i doubt it) – da Bich Feb 23 '16 at 15:29
  • consider if the cost of a single select count(*) issued before your processing would hurt performance, if not, go with it. If your processing is a single statement, you can get number of affected rows after the statement is executed. – BigMike Feb 23 '16 at 15:32
  • @LanceJava, i saw that too.. but it seems my library ( java.sql.Connection.prepareStatement) only allows for a second argument for preparestatement that is something to do with autogeneratedKeys.. not sure how I can use that other method? – da Bich Feb 23 '16 at 15:34
  • @BigMike.. yeah was contemplating the count(*) thing.. but it's quite involved as my query is being run in a separate class.. and the operations are done elsewhere..so I'd have to start passing around the record count between my classes, along with the resultset.. It's doable - but I'd rather just make use of the resultset if I can – da Bich Feb 23 '16 at 15:39
  • Why do you need `ResultSet.CONCUR_UPDATABLE`? You probably only need `ResultSet.CONCUR_READ_ONLY` – 4castle Feb 23 '16 at 15:42
  • well..@LanceJava and @4castle were right.. I was just missing the 3rd parameter to use the right method! forehead to desk now. – da Bich Feb 23 '16 at 15:51
  • Please select my answer in that case :) – 4castle Feb 23 '16 at 15:58
  • Rework your code and count records the proper way (with a select count(*)), you may see this like a waste of time now, but will save you from future headaches. Scrolling 2 times data it's just "bad". – BigMike Feb 24 '16 at 08:26
  • thanks.. noted for future apps that might require such scalability. My data volumes and the load on the dbms is so low, this is really not a concern here. – da Bich Feb 24 '16 at 16:21

2 Answers2

12

Use

PreparedStatement pStatement = conn.prepareStatement(tmpSQL,
                                        ResultSet.TYPE_SCROLL_INSENSITIVE,
                                        ResultSet.CONCUR_UPDATABLE);

Java Doc Info

Then to get the count of records in your ResultSet, use rset.last() followed by rset.getRow(). Then use rset.beforeFirst() to put the cursor back to where it was initially.

4castle
  • 32,613
  • 11
  • 69
  • 106
  • You only need `ResultSet.CONCUR_UPDATABLE` if you plan on using the `updateXXX` methods on your ResultSet object. Otherwise, just put `ResultSet.CONCUR_READ_ONLY` – 4castle Feb 23 '16 at 15:49
1

Some initial background comments

Scrollability is mostly depending on the underlying database. Even though JDBC has a method to scroll back, it is not implemented e.g. in Oracle JDBC driver.

I would suggest to avoid of scrolling the result set. In fact even if it works for some databases, it is quite inefficient to implement. Also inefficient to use on the GUI, since each scrolling would then trigger a database operation, which is slow.

The usual approach is to load all rows to a container (e.g. List<...> ) and process that, if you have a moderate number of rows (say up to 1000 rows). If you have a lot more rows, then:

  • think it over if you really need to read that many rows. For example, if this is a GUI list, it may not make sense loading 1 million rows, since the human user will not one-by-one scroll trough all 1 million rows. Probably a better filtering and/or pagination would make sense.
  • if you really need all the rows for business side processing, then think it over. Pulling all rows from the database to the app for processing is a super-inefficient programming pattern. Use stored procedures, or packages (Oracle) to process your data on the database side.
  • but if you really really need to pull like 1 millon rows to the app for processing, do the processing in a streaming-manner. I.e. instead of first fetching 1 million rows to the memory and then processing it, fetch one row, process it, fetch another row, process it. This also explains why back-scrolling is usually not supported: that would require the driver or the db to actually hold in memory one million rows of the result of your select, because you might want to scroll back.

To solve your question

To get the count of records, execute a separate statement with select count(*). Then execute another select to actually read the records and fetch them (only forward).

It is much faster than reading all records just to count them.

Gee Bee
  • 1,794
  • 15
  • 17
  • You don't have to read all the records to count them. See my answer. – 4castle Feb 23 '16 at 16:02
  • I am saying the same thing :) do select count( * ) first to count the records and then use a standard forward only result set to read the actual data. I believe the actual data *is* needed later no? Otherwise what is the point to scroll back to the beginning to read it all over? – Gee Bee Feb 23 '16 at 16:26
  • The point to scrolling back to the beginning is so that you _can_ use the data later. This is what scrolling is meant to do. – 4castle Feb 23 '16 at 16:40
  • 1
    Ok, 4castle, I might not perfectly understand your point. Can you elaborate please? I think that for counting the number of rows, no scrolling and nothing is needed, just select count( * ). Any other solutions are so resource heavy that we can call them pointless. For reading the data, we shall use a standard forward-only non- updateable read-only result set, because it is the most efficient and supported by all JDBC implementations. Scrolling back is actually not supported in many JDBC implementations, therefore I suggest not even consider using such as fragile solution. – Gee Bee Feb 23 '16 at 17:10
  • If that is true, then that is a very good reason to use a count(*) call instead. Could you please cite where you are getting your information on the drawbacks of scrollable ResultSets? I am referencing http://stackoverflow.com/questions/7886462/how-to-get-row-count-using-resultset-in-java for my answer, and I can see your point in the event of a very large table. – 4castle Feb 24 '16 at 03:57
  • I've done more research on the topic, and I've found that the scrollable ResultSet feature is available on all database drivers that are JDBC 2.0+. So I have not found it to be a fragile solution at all (the current JDBC specification is 4.0). [Ref](http://www.ibm.com/developerworks/library/j-5things10/) – 4castle Feb 24 '16 at 04:20
  • Update. More research has been done. And I've yet to find a common, most recently versioned database driver that does **not** support scrollable ResultSets. In fact, some ONLY support scrollable ResultSets. Considering the OP needs to use the data after counting the records, scrollable is definitely the best choice for this, regardless of table size. – 4castle Feb 24 '16 at 04:40
  • Simply because it can be done, doesn't mean it must be done. One sunny day your DMBS may run out of cursors or your tables may get fragmented, and your double scroll would start taking 10 minutes to fetch 5 rows. The count(*) is the "BEST" way to count records ahead, with right indexes (and ofc real DBMS, not toys) it may also just use indexes. And this without even taking into account concurrency, while you scroll the ResultSet you allow, at best, dirty reads, may be good on a single user application, but may give headaches when concurrency becomes an issue. – BigMike Feb 24 '16 at 08:25
  • Oracle scrollable result set for JDBC2.0 is implemented in the client side. That means if you have a result set for 1 million rows, all the 1 million rows will be kept in the java heap. https://docs.oracle.com/cd/A87860_01/doc/java.817/a83724/resltse1.htm I am referring to "Do not specify scrollability for a large result set." on that page. – Gee Bee Feb 24 '16 at 10:01
  • Another limitation is that: "As of 10gr1, Scrollability, positioning, sensitivity and updatability none of these features are implemented for CallableStatement objects in Oracle JDBC drivers." https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:311216724461 It means in short that if you stick with scrollable result sets, you have to give up the **whole** power of database-side data processing. That hurts. It is considered as a bad idea to do selects, and SQLs in the application code from the 80s for performance reasons. – Gee Bee Feb 24 '16 at 10:04
  • 1
    (Comments are restricted in length) so the final comment: "I've never seen the need for a scrollable cursor (implies WAY too much procedural code is involved)." - Tom Kyte, https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:311216724461 And this must be correct. You shall use the power of the 4th generation language, SQL to process your data, instead of pulling all rows to the application side, and read it trough back and forth. Doing that does not scale. – Gee Bee Feb 24 '16 at 10:06