Retrieving and Modifying Values from Result Sets
The following method,CoffeesTable.viewTableoutputs the contents of theCOFFEEStables, and demonstrates the use ofResultSetobjects 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(); } }AResultSetobject 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.viewTablemethod creates aResultSet,rs, when it executes the query through theStatementobject,stmt. Note that aResultSetobject can be created through any object that implements theStatementinterface, includingPreparedStatement,CallableStatement, andRowSet.
You access the data in aResultSetobject 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.nextmoves the cursor to the next row. This method returnsfalseif the cursor is positioned after the last row. This method repeatedly calls theResultSet.nextmethod with awhileloop to iterate through all the data in theResultSet.
ResultSet Interface
TheResultSetinterface provides methods for retrieving and manipulating the results of executed queries, andResultSetobjects can have different functionality and characteristics. These characteristics are type, concurrency, and cursor holdability.
ResultSet Types
The type of aResultSetobject 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 theResultSetobject.
The sensitivity of aResultSetobject is determined by one of three differentResultSettypes:
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.ResultSettype isTYPE_FORWARD_ONLY.
ResultSet Concurrency
The concurrency of aResultSetobject determines what level of update functionality is supported.
There are two concurrency levels:
The default
CONCUR_READ_ONLY: TheResultSetobject cannot be updated using theResultSetinterface.CONCUR_UPDATABLE: TheResultSetobject can be updated using theResultSetinterface.ResultSetconcurrency isCONCUR_READ_ONLY.
Note: Not all JDBD drivers and databases support concurrency. The methodDatabaseMetaData.supportsResultSetConcurrencyreturnstrueif the specified concurrency level is supported by the driver andfalseotherwise.
The methodCoffeesTable.modifyPricesdemonstrates how to use aResultSetobject whose concurrency level isCONCUR_UPDATABLE.
Cursor Holdability
Calling the methodConnection.commitcan close theResultSetobjects that have been created during the current transaction. In some cases, however, this may not be the desired behavior. TheResultSetproperty holdability gives the application control over whetherResultSetobjects (cursors) are closed when commit is called.
The followingResultSetconstants may be supplied to theConnectionmethodscreateStatement,prepareStatement, andprepareCall:
The default cursor holdability varies depending on your DBMS.
HOLD_CURSORS_OVER_COMMIT:ResultSetcursors are not closed; they are holdable: they are held open when the methodcommitis called. Holdable cursors might be ideal if your application uses mostly read-onlyResultSetobjects.CLOSE_CURSORS_AT_COMMIT:ResultSetobjects (cursors) are closed when thecommitmethod 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 ofResultSetobjects and whetherHOLD_CURSORS_OVER_COMMITandCLOSE_CURSORS_AT_COMMITare 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
TheResultSetinterface declares getter methods (for example,getBooleanandgetLong) 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 SQLASclause.
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 theResultSetrsisCOF_NAME, which stores a value of SQL typeVARCHAR. The method for retrieving a value of SQL typeVARCHARisgetString. 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 methodgetStringis recommended for retrieving the SQL typesCHARandVARCHAR, it is possible to retrieve any of the basic SQL types with it. Getting all values withgetStringcan be very useful, but it also has its limitations. For instance, if it is used to retrieve a numeric type,getStringconverts the numeric value to a JavaStringobject, 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 thegetStringmethod.
Cursors
As mentioned previously, you access the data in aResultSetobject through a cursor, which points to one row in theResultSetobject. However, when aResultSetobject is first created, the cursor is positioned before the first row. The methodCoffeeTables.viewTablemoves the cursor by calling theResultSet.nextmethod. There are other methods available to move the cursor:
Note that the default sensitivity of a
next: Moves the cursor forward one row. Returnstrueif the cursor is now positioned on a row andfalseif the cursor is positioned after the last row.previous: Moves the cursor backward one row. Returnstrueif the cursor is now positioned on a row andfalseif the cursor is positioned before the first row.first: Moves the cursor to the first row in theResultSetobject. Returnstrueif the cursor is now positioned on the first row andfalseif theResultSetobject does not contain any rows.last:: Moves the cursor to the last row in theResultSetobject. Returnstrueif the cursor is now positioned on the last row andfalseif theResultSetobject does not contain any rows.beforeFirst: Positions the cursor at the start of theResultSetobject, before the first row. If theResultSetobject does not contain any rows, this method has no effect.afterLast: Positions the cursor at the end of theResultSetobject, after the last row. If theResultSetobject 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.ResultSetisTYPE_FORWARD_ONLY, which means that it cannot be scrolled; you cannot call any of these methods that move the cursor, exceptnext, if yourResultSetcannot 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 defaultResultSetobject, and you can only move its cursor forward. However, you can createResultSetobjects that can be scrolled (the cursor can move backwards or move to an absolute position) and updated.
The following method,CoffeesTable.modifyPrices, multiplies thePRICEcolumn 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_SENSITIVEcreates aResultSetobject whose cursor can move both forward and backward relative to the current position and to an absolute position. The fieldResultSet.CONCUR_UPDATABLEcreates aResultSetobject that can be updated. See theResultSetJavadoc for other fields you can specify to modify the behavior ofResultSetobjects.
The methodResultSet.updateFloatupdates the specified column (in this example,PRICEwith the specifiedfloatvalue in the row where the cursor is positioned.ResultSetcontains 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.updateRowto update the database.
Using Statement Objects for Batch Updates
Statement,PreparedStatementandCallableStatementobjects 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 TABLEandDROP TABLE. It cannot, however, contain a statement that would produce aResultSetobject, such as aSELECTstatement. In other words, the list can contain only statements that produce an update count.
The list, which is associated with aStatementobject at its creation, is initially empty. You can add SQL commands to this list with the methodaddBatchand empty it with the methodclearBatch. When you have finished adding statements to the list, call the methodexecuteBatchto send them all to the database to be executed as a unit, or batch.
For example, the following methodCoffeesTable.batchUpdateadds four rows to theCOFFEEStable 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 theConnectionobject con so that the transaction will not be automatically committed or rolled back when the methodexecuteBatchis 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.addBatchadds a command to the list of commands associated with theStatementobjectstmt. In this example, these commands are allINSERT INTOstatements, each one adding a row consisting of five column values. The values for the columnsCOF_NAMEandPRICEare 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 columnsSALESandTOTAL, all start out being zero because there have been no sales yet. (SALESis the number of pounds of this row's coffee sold in the current week;TOTALis 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 thatstmtuses the methodexecuteBatchto 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,updateCountswill contain four values, all of which are 1 because an insertion affects one row. The list of commands associated withstmtwill now be empty because the four commands added previously were sent to the database whenstmtcalled the methodexecuteBatch. You can at any time explicitly empty this list of commands with the methodclearBatch.
TheConnection.commitmethod makes the batch of updates to theCOFFEEStable 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 currentConnectionobject.
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, whereconis aConnectionobject:
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 aBatchUpdateExceptionwhen you call the methodexecuteBatchif (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 (aSELECTstatement) 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 theexecuteBatchmethod.
ABatchUpdateExceptioncontains 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.
BatchUpdateExceptionis derived fromSQLException. This means that you can use all of the methods available to anSQLExceptionobject with it. The following method,JDBCTutorialUtilities.printBatchUpdateExceptionprints all of theSQLExceptioninformation plus the update counts contained in aBatchUpdateExceptionobject. BecauseBatchUpdateException.getUpdateCountsreturns an array ofint, the code uses aforloop 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 theResultSetinterface. If you attempt to insert a new row and your JDBC driver database does not support this feature, aSQLFeatureNotSupportedExceptionexception is thrown.
The following method,CoffeesTable.insertRow, inserts a row into theCOFFEESthrough aResultSetobject:
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.moveToInsertRowmoves 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.updateStringto update the insert row'sCOF_NAMEcolumn toKona.
The methodResultSet.insertRowinserts the contents of the insert row into theResultSetobject and into the database.
No comments:
Post a Comment