Retrieving and Modifying Values from Result Sets
The following method,CoffeesTable.viewTable
outputs the contents of theCOFFEES
tables, and demonstrates the use ofResultSet
objects and cursors:
public static void viewTable(Connection con) throws SQLException { Statement stmt = null; String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from " + dbName + ".COFFEES"; try { stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String coffeeName = rs.getString("COF_NAME"); int supplierID = rs.getInt("SUP_ID"); float price = rs.getFloat("PRICE"); int sales = rs.getInt("SALES"); int total = rs.getInt("TOTAL"); System.out.println(coffeeName + "\t" + supplierID + "\t" + price + "\t" + sales + "\t" + total); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); } finally { stmt.close(); } }AResultSet
object is a table of data representing a database result set, which is usually generated by executing a statement that queries the database. For example, theCoffeeTables.viewTable
method creates aResultSet
,rs
, when it executes the query through theStatement
object,stmt
. Note that aResultSet
object can be created through any object that implements theStatement
interface, includingPreparedStatement
,CallableStatement
, andRowSet
.
You access the data in aResultSet
object through a cursor. Note that this cursor is not a database cursor. This cursor is a pointer that points to one row of data in theResultSet
. Initially, the cursor is positioned before the first row. The methodResultSet.next
moves the cursor to the next row. This method returnsfalse
if the cursor is positioned after the last row. This method repeatedly calls theResultSet.next
method with awhile
loop to iterate through all the data in theResultSet
.
ResultSet Interface
TheResultSet
interface provides methods for retrieving and manipulating the results of executed queries, andResultSet
objects can have different functionality and characteristics. These characteristics are type, concurrency, and cursor holdability.
ResultSet Types
The type of aResultSet
object determines the level of its functionality in two areas: the ways in which the cursor can be manipulated, and how concurrent changes made to the underlying data source are reflected by theResultSet
object.
The sensitivity of aResultSet
object is determined by one of three differentResultSet
types:
The default
TYPE_FORWARD_ONLY
: The result set cannot be scrolled; its cursor moves forward only, from before the first row to after the last row. The rows contained in the result set depend on how the underlying database generates the results. That is, it contains the rows that satisfy the query at either the time the query is executed or as the rows are retrieved.TYPE_SCROLL_INSENSITIVE
: The result cannot be scrolled; its cursor can move both forward and backward relative to the current position, and it can move to an absolute position. The result set is insensitive to changes made to the underlying data source while it is open. It contains the rows that satisfy the query at either the time the query is executed or as the rows are retrieved.TYPE_SCROLL_SENSITIVE
: The result can be scrolled; its cursor can move both forward and backward relative to the current position, and it can move to an absolute position. The result set reflects changes made to the underlying data source while the result set remains open.ResultSet
type isTYPE_FORWARD_ONLY
.
ResultSet Concurrency
The concurrency of aResultSet
object determines what level of update functionality is supported.
There are two concurrency levels:
The default
CONCUR_READ_ONLY
: TheResultSet
object cannot be updated using theResultSet
interface.CONCUR_UPDATABLE
: TheResultSet
object can be updated using theResultSet
interface.ResultSet
concurrency isCONCUR_READ_ONLY
.
Note: Not all JDBD drivers and databases support concurrency. The methodDatabaseMetaData.supportsResultSetConcurrency
returnstrue
if the specified concurrency level is supported by the driver andfalse
otherwise.
The methodCoffeesTable.modifyPrices
demonstrates how to use aResultSet
object whose concurrency level isCONCUR_UPDATABLE
.
Cursor Holdability
Calling the methodConnection.commit
can close theResultSet
objects that have been created during the current transaction. In some cases, however, this may not be the desired behavior. TheResultSet
property holdability gives the application control over whetherResultSet
objects (cursors) are closed when commit is called.
The followingResultSet
constants may be supplied to theConnection
methodscreateStatement
,prepareStatement
, andprepareCall
:
The default cursor holdability varies depending on your DBMS.
HOLD_CURSORS_OVER_COMMIT
:ResultSet
cursors are not closed; they are holdable: they are held open when the methodcommit
is called. Holdable cursors might be ideal if your application uses mostly read-onlyResultSet
objects.CLOSE_CURSORS_AT_COMMIT
:ResultSet
objects (cursors) are closed when thecommit
method is called. Closing cursors when this method is called can result in better performance for some applications.
Note: Not all JDBD drivers and databases support holdable and non-holdable cursors. The following method,JDBCTutorialUtilities.cursorHoldabilitySupport
, outputs the default cursor holdability ofResultSet
objects and whetherHOLD_CURSORS_OVER_COMMIT
andCLOSE_CURSORS_AT_COMMIT
are supported:
public static void cursorHoldabilitySupport(Connection conn) throws SQLException { DatabaseMetaData dbMetaData = conn.getMetaData(); System.out.println("ResultSet.HOLD_CURSORS_OVER_COMMIT = " + ResultSet.HOLD_CURSORS_OVER_COMMIT); System.out.println("ResultSet.CLOSE_CURSORS_AT_COMMIT = " + ResultSet.CLOSE_CURSORS_AT_COMMIT); System.out.println("Default cursor holdability: " + dbMetaData.getResultSetHoldability()); System.out.println("Supports HOLD_CURSORS_OVER_COMMIT? " + dbMetaData.supportsResultSetHoldability( ResultSet.HOLD_CURSORS_OVER_COMMIT)); System.out.println("Supports CLOSE_CURSORS_AT_COMMIT? " + dbMetaData.supportsResultSetHoldability( ResultSet.CLOSE_CURSORS_AT_COMMIT)); }Retrieving Column Values from Rows
TheResultSet
interface declares getter methods (for example,getBoolean
andgetLong
) for retrieving column values from the current row. You can retrieve values using either the index number of the column or the name of the column. The column index is usually more efficient. Columns are numbered from 1. For maximum portability, result set columns within each row should be read in left-to-right order, and each column should be read only once.
For example, the following method,CoffeesTable.alternateViewTable
, retrieves column values by number:
public static void alternateViewTable(Connection con) throws SQLException { Statement stmt = null; String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES"; try { stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String coffeeName = rs.getString(1); int supplierID = rs.getInt(2); float price = rs.getFloat(3); int sales = rs.getInt(4); int total = rs.getInt(5); System.out.println(coffeeName + "\t" + supplierID + "\t" + price + "\t" + sales + "\t" + total); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); } finally { stmt.close(); } }Column names used as input to getter methods are case-insensitive. When a getter method is called with a column name and several columns have the same name, the value of the first matching column is returned. The column name option is designed to be used when column names are used in the SQL query that generated the result set. For columns that are not explicitly named in the query, it is best to use column numbers. If column names are used, the developer should guarantee that they uniquely refer to the intended columns, which can be ensured by using the SQLAS
clause.
The getter method of the appropriate type retrieves the value in each column. For example, in the methodCoffeeTables.viewTable
, the first column in each row of theResultSet
rs
isCOF_NAME
, which stores a value of SQL typeVARCHAR
. The method for retrieving a value of SQL typeVARCHAR
isgetString
. The second column in each row stores a value of SQL typeINTEGER
, and the method for retrieving values of that type isgetInt
.
Note that although the methodgetString
is recommended for retrieving the SQL typesCHAR
andVARCHAR
, it is possible to retrieve any of the basic SQL types with it. Getting all values withgetString
can be very useful, but it also has its limitations. For instance, if it is used to retrieve a numeric type,getString
converts the numeric value to a JavaString
object, and the value has to be converted back to a numeric type before it can be operated on as a number. In cases where the value is treated as a string anyway, there is no drawback. Furthermore, if you want an application to retrieve values of any standard SQL type other than SQL3 types, use thegetString
method.
Cursors
As mentioned previously, you access the data in aResultSet
object through a cursor, which points to one row in theResultSet
object. However, when aResultSet
object is first created, the cursor is positioned before the first row. The methodCoffeeTables.viewTable
moves the cursor by calling theResultSet.next
method. There are other methods available to move the cursor:
Note that the default sensitivity of a
next
: Moves the cursor forward one row. Returnstrue
if the cursor is now positioned on a row andfalse
if the cursor is positioned after the last row.previous
: Moves the cursor backward one row. Returnstrue
if the cursor is now positioned on a row andfalse
if the cursor is positioned before the first row.first
: Moves the cursor to the first row in theResultSet
object. Returnstrue
if the cursor is now positioned on the first row andfalse
if theResultSet
object does not contain any rows.last:
: Moves the cursor to the last row in theResultSet
object. Returnstrue
if the cursor is now positioned on the last row andfalse
if theResultSet
object does not contain any rows.beforeFirst
: Positions the cursor at the start of theResultSet
object, before the first row. If theResultSet
object does not contain any rows, this method has no effect.afterLast
: Positions the cursor at the end of theResultSet
object, after the last row. If theResultSet
object does not contain any rows, this method has no effect.relative(int rows)
: Moves the cursor relative to its current position.absolute(int row)
: Positions the cursor on the row specified by the parameterrow
.ResultSet
isTYPE_FORWARD_ONLY
, which means that it cannot be scrolled; you cannot call any of these methods that move the cursor, exceptnext
, if yourResultSet
cannot be scrolled. The methodCoffeesTable.modifyPrices
, described in the following section, demonstrates how you can move the cursor of aResultSet
.
Updating Rows in ResultSet Objects
You cannot update a defaultResultSet
object, and you can only move its cursor forward. However, you can createResultSet
objects that can be scrolled (the cursor can move backwards or move to an absolute position) and updated.
The following method,CoffeesTable.modifyPrices
, multiplies thePRICE
column of each row by the argumentpercentage
:
public void modifyPrices(float percentage) throws SQLException { Statement stmt = null; try { stmt = con.createStatement(); stmt = con.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet uprs = stmt.executeQuery( "SELECT * FROM " + dbName + ".COFFEES"); while (uprs.next()) { float f = uprs.getFloat("PRICE"); uprs.updateFloat("PRICE", f * percentage); uprs.updateRow(); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); } finally { stmt.close(); } }The fieldResultSet.TYPE_SCROLL_SENSITIVE
creates aResultSet
object whose cursor can move both forward and backward relative to the current position and to an absolute position. The fieldResultSet.CONCUR_UPDATABLE
creates aResultSet
object that can be updated. See theResultSet
Javadoc for other fields you can specify to modify the behavior ofResultSet
objects.
The methodResultSet.updateFloat
updates the specified column (in this example,PRICE
with the specifiedfloat
value in the row where the cursor is positioned.ResultSet
contains various updater methods that enable you to update column values of various data types. However, none of these updater methods modifies the database; you must call the methodResultSet.updateRow
to update the database.
Using Statement Objects for Batch Updates
Statement
,PreparedStatement
andCallableStatement
objects have a list of commands that is associated with them. This list may contain statements for updating, inserting, or deleting a row; and it may also contain DDL statements such asCREATE TABLE
andDROP TABLE
. It cannot, however, contain a statement that would produce aResultSet
object, such as aSELECT
statement. In other words, the list can contain only statements that produce an update count.
The list, which is associated with aStatement
object at its creation, is initially empty. You can add SQL commands to this list with the methodaddBatch
and empty it with the methodclearBatch
. When you have finished adding statements to the list, call the methodexecuteBatch
to send them all to the database to be executed as a unit, or batch.
For example, the following methodCoffeesTable.batchUpdate
adds four rows to theCOFFEES
table with a batch update:
public void batchUpdate() throws SQLException { Statement stmt = null; try { this.con.setAutoCommit(false); stmt = this.con.createStatement(); stmt.addBatch("INSERT INTO COFFEES " + "VALUES('Amaretto', 49, 9.99, 0, 0)"); stmt.addBatch("INSERT INTO COFFEES " + "VALUES('Hazelnut', 49, 9.99, 0, 0)"); stmt.addBatch("INSERT INTO COFFEES " + "VALUES('Amaretto_decaf', 49, 10.99, 0, 0)"); stmt.addBatch("INSERT INTO COFFEES " + "VALUES('Hazelnut_decaf', 49, 10.99, 0, 0)"); int [] updateCounts = stmt.executeBatch(); this.con.commit(); } catch(BatchUpdateException b) { JDBCTutorialUtilities.printBatchUpdateException(b); } catch(SQLException ex) { JDBCTutorialUtilities.printSQLException(ex); } finally { this.con.setAutoCommit(true); stmt.close(); } }The following line disables auto-commit mode for theConnection
object con so that the transaction will not be automatically committed or rolled back when the methodexecuteBatch
is called.
this.con.setAutoCommit(false);To allow for correct error handling, you should always disable auto-commit mode before beginning a batch update.
The methodStatement.addBatch
adds a command to the list of commands associated with theStatement
objectstmt
. In this example, these commands are allINSERT INTO
statements, each one adding a row consisting of five column values. The values for the columnsCOF_NAME
andPRICE
are the name of the coffee and its price, respectively. The second value in each row is 49 because that is the identification number for the supplier, Superior Coffee. The last two values, the entries for the columnsSALES
andTOTAL
, all start out being zero because there have been no sales yet. (SALES
is the number of pounds of this row's coffee sold in the current week;TOTAL
is the total of all the cumulative sales of this coffee.)
The following line sends the four SQL commands that were added to its list of commands to the database to be executed as a batch:
int [] updateCounts = stmt.executeBatch();Note thatstmt
uses the methodexecuteBatch
to send the batch of insertions, not the methodexecuteUpdate
, which sends only one command and returns a single update count. The DBMS executes the commands in the order in which they were added to the list of commands, so it will first add the row of values for Amaretto, then add the row for Hazelnut, then Amaretto decaf, and finally Hazelnut decaf. If all four commands execute successfully, the DBMS will return an update count for each command in the order in which it was executed. The update counts that indicate how many rows were affected by each command are stored in the arrayupdateCounts
.
If all four of the commands in the batch are executed successfully,updateCounts
will contain four values, all of which are 1 because an insertion affects one row. The list of commands associated withstmt
will now be empty because the four commands added previously were sent to the database whenstmt
called the methodexecuteBatch
. You can at any time explicitly empty this list of commands with the methodclearBatch
.
TheConnection.commit
method makes the batch of updates to theCOFFEES
table permanent. This method needs to be called explicitly because the auto-commit mode for this connection was disabled previously.
The following line enables auto-commit mode for the currentConnection
object.
this.con.setAutoCommit(true);Now each statement in the example will automatically be committed after it is executed, and it no longer needs to invoke the methodcommit
.
Performing Parameterized Batch Update
It is also possible to have a parameterized batch update, as shown in the following code fragment, wherecon
is aConnection
object:
con.setAutoCommit(false); PreparedStatement pstmt = con.prepareStatement( "INSERT INTO COFFEES VALUES( ?, ?, ?, ?, ?)"); pstmt.setString(1, "Amaretto"); pstmt.setInt(2, 49); pstmt.setFloat(3, 9.99); pstmt.setInt(4, 0); pstmt.setInt(5, 0); pstmt.addBatch(); pstmt.setString(1, "Hazelnut"); pstmt.setInt(2, 49); pstmt.setFloat(3, 9.99); pstmt.setInt(4, 0); pstmt.setInt(5, 0); pstmt.addBatch(); // ... and so on for each new type of coffee int [] updateCounts = pstmt.executeBatch(); con.commit(); con.setAutoCommit(true);Handling Batch Update Exceptions
You will get aBatchUpdateException
when you call the methodexecuteBatch
if (1) one of the SQL statements you added to the batch produces a result set (usually a query) or (2) one of the SQL statements in the batch does not execute successfully for some other reason.
You should not add a query (aSELECT
statement) to a batch of SQL commands because the methodexecuteBatch
, which returns an array of update counts, expects an update count from each SQL statement that executes successfully. This means that only commands that return an update count (commands such asINSERT INTO
,UPDATE
,DELETE
) or that return 0 (such asCREATE TABLE
,DROP TABLE
,ALTER TABLE
) can be successfully executed as a batch with theexecuteBatch
method.
ABatchUpdateException
contains an array of update counts that is similar to the array returned by the methodexecuteBatch
. In both cases, the update counts are in the same order as the commands that produced them. This tells you how many commands in the batch executed successfully and which ones they are. For example, if five commands executed successfully, the array will contain five numbers: the first one being the update count for the first command, the second one being the update count for the second command, and so on.
BatchUpdateException
is derived fromSQLException
. This means that you can use all of the methods available to anSQLException
object with it. The following method,JDBCTutorialUtilities.printBatchUpdateException
prints all of theSQLException
information plus the update counts contained in aBatchUpdateException
object. BecauseBatchUpdateException.getUpdateCounts
returns an array ofint
, the code uses afor
loop to print each of the update counts:
public static void printBatchUpdateException(BatchUpdateException b) { System.err.println("----BatchUpdateException----"); System.err.println("SQLState: " + b.getSQLState()); System.err.println("Message: " + b.getMessage()); System.err.println("Vendor: " + b.getErrorCode()); System.err.print("Update counts: "); int [] updateCounts = b.getUpdateCounts(); for (int i = 0; i < updateCounts.length; i++) { System.err.print(updateCounts[i] + " "); } }Inserting Rows in ResultSet Objects
Note: Not all JDBD drivers support inserting new rows with theResultSet
interface. If you attempt to insert a new row and your JDBC driver database does not support this feature, aSQLFeatureNotSupportedException
exception is thrown.
The following method,CoffeesTable.insertRow
, inserts a row into theCOFFEES
through aResultSet
object:
public void insertRow(String coffeeName, int supplierID, float price, int sales, int total) throws SQLException { Statement stmt = null; try { stmt = con.createStatement(); stmt = con.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet uprs = stmt.executeQuery( "SELECT * FROM " + dbName + ".COFFEES"); uprs.moveToInsertRow(); uprs.updateString("COF_NAME", coffeeName); uprs.updateInt("SUP_ID", supplierID); uprs.updateFloat("PRICE", price); uprs.updateInt("SALES", sales); uprs.updateInt("TOTAL", total); uprs.insertRow(); uprs.beforeFirst(); } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); } finally { stmt.close(); } }The methodResultSet.moveToInsertRow
moves the cursor to the insert row. The insert row is a special row associated with an updatable result set. It is essentially a buffer where a new row can be constructed by calling the updater methods prior to inserting the row into the result set. For example, this method calls the methodResultSet.updateString
to update the insert row'sCOF_NAME
column toKona
.
The methodResultSet.insertRow
inserts the contents of the insert row into theResultSet
object and into the database.
No comments:
Post a Comment