Spec-Zone .ru
спецификации, руководства, описания, API
|
NOTE: The material in this chapter is based on JDBCtm API Tutorial and Reference, Second Edition: Universal Data Access for the Javatm 2 Platform, published by Addison Wesley as part of the Java series, ISBN 0-201-43328-1.
A ResultSet
is a Java object
that contains the results of executing an SQL query. In other
words, it contains the rows that satisfy the conditions of the
query. The data stored in a ResultSet
object is
retrieved through a set of get
methods that allows
access to the various columns of the current row. The
ResultSet.next
method is used to move to the next row
of the ResultSet
, making it the current row.
The general form of a result set is a
table with column headings and the corresponding values returned by
a query. For example, if your query is SELECT a, b, c FROM
Table1
, your result set will have the following form:
a b c ---------- ------------ ----------- 12345 Cupertino 2459723.495 83472 Redmond 1.0 83492 Boston 35069473.43
The following code fragment is an example
of executing an SQL statement that will return a collection of
rows, with column a
as an int
, column
b
as a String
, and column c
as a float:
java.sql.Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1"); while (rs.next()) { // retrieve and print the values for the current row int i = rs.getInt("a"); String s = rs.getString("b"); float f = rs.getFloat("c"); System.out.println("ROW = " + i + " " + s + " " + f); }
A relational database is made up of tables, with each table consisting of rows and columns. A row in a relational database table can be thought of as representing an instance of the entity that the table represents. For example, if there is a table of employees, each row will contain information about a particular employee. Each piece of data about the employee is stored in a column, so, for instance, the table of employees could have columns for an identification number, a name, a salary, and a date of hire. The columns in a row would contain the ID number, name, salary, and date of hire for a particular employee.
A result set is also a table with rows and columns, but it contains only the column values from a database table that satisfy the conditions of a query. In other words, a result set row will contain a subset of the columns in the underlying database table (unless the query selects everything in the table, in which case the result set table will include all of the column values for every row in the database table). In the past, a column value in a relational database table (and consequently in a result set table) had to be atomic; that is, it could be only one indivisible value. For instance, an array could not be a column value because an array may be made up of multiple elements. With the advent of SQL3 data types, however, the permissible content of table columns has expanded dramatically. It is now possible for an array or even a user-defined structured type to be a column value. Because this new capability allows a relational database to store instances of complex types as column values, it makes a relational database more like an object database, blurring the distinction between relational and object databases. Programmers can take advantage of these new data types if they use a JDBC 2.0 driver that supports SQL3 types.
A ResultSet
object maintains
a cursor, which points to its current row of data. The cursor moves
down one row each time the method next
is called. When
a ResultSet
object is first created, the cursor is
positioned before the first row, so the first call to the
next
method puts the cursor on the first row, making
it the current row. ResultSet
rows can be retrieved in
sequence from top to bottom as the cursor moves down one row with
each successive call to the method next
. This ability
to move its cursor only forward is the default behavior for a
ResultSet
and is the only cursor movement possible
with drivers that implement only the JDBC 1.0 API. This kind of
result set has the type ResultSet.TYPE_FORWARD_ONLY
and is referred to as a forward only result set.
If a driver implements the cursor
movement methods in the JDBC 2.0 core API, its result sets can be
scrollable. A scrollable result set's cursor can move both forward
and backward as well as to a particular row. The following methods
move the cursor backward, to the first row, to the last row, to a
particular row number, to a specified number of rows from the
current row, and so on: previous
, first
,
last
, absolute
, relative
,
afterLast
, and beforeFirst
. An
explanation and example of how to make a result set scrollable will
be presented in the section "Creating Different Types of Result Sets"
on page 55.
When a cursor is positioned on a row in a
ResultSet
object (not before the first row or after
the last row), that row becomes the current row. This means that
any methods called while the cursor is positioned on that row will
(1) operate on values in that row (methods such as
getXXX
and updateXXX
), (2) operate on the
row as a whole (such as the methods updateRow
,
insertRow
, deleteRow
,
refresh-Row
), or (3) use that row as a starting point
for moving to other rows (such as the method
relative
).
A cursor remains valid until the
ResultSet
object or its parent Statement
object is closed.
As stated in the previous section, the
standard cursor movement for forward only result sets is to use the
method next
to iterate through each row of a result
set once from top to bottom. With scrollable result sets, it is
possible to revisit a row or to iterate through the result set
multiple times. This is possible because the cursor can be moved
before the first row at any time (with the method beforeFirst.
The cursor can begin another iteration through the result set with
the method next
. The following example positions the
cursor before the first row and then iterates forward through the
contents of the result set. The methods getString
and
getFloat
retrieve the column values for each row until
there are no more rows, at which time the method next
returns the value false
.
rs.beforeFirst(); while (rs.next()) { System.out.println(rs.getString("EMP_NO") + " " + rs.getFloat("SALARY"); }
It is also possible to iterate through a
result set backwards, as is shown in the next example. The cursor
is first moved to the very end of the result set (with the method
afterLast
), and then the method previous
is invoked within a while
loop to iterate through the
contents of the result set by moving to the previous row with each
iteration. The method previous
returns
false
when there are no more rows, so the loop ends
after all the rows have been visited.
rs.afterLast(); while (rs.previous()) { System.out.println(rs.getString("EMP_NO") + " " + rs.getFloat("SALARY"); }
The interface ResultSet
offers still other ways to iterate through the rows of a scrollable
result set. Care should be taken, however, to avoid incorrect
alternatives such as the one illustrated in the following
example:
// incorrect! while (!rs.isAfterLast()) { rs.relative(1); System.out.println( rs.getString("EMP_NO") + " " + rs.getFloat("SALARY")); }
This example attempts to iterate forward
through a scrollable result set and is incorrect for several
reasons. One error is that if ResultSet.isAfterLast
is
called when the result set is empty, it will return a value of
false
since there is no last row. The loop body will
be executed, which is not what is wanted. An additional problem
occurs when the cursor is positioned before the first row of a
result set that contains data. In this case, calling
rs.relative(1)
is erroneous because there is no
current row. The method relative
moves the cursor the
specified number of rows from the current row, and it must be
invoked only while the cursor is on the current row.
The following code fragment fixes the
problems in the previous example. Here a call to the method
ResultSet.first
is used to distinguish the case of an
empty result set from one that contains data. Because
ResultSet.isAfterLast
is called only when the result
set is non-empty, the loop control works correctly. Since
ResultSet.
first
method initially
positions the cursor on the first row, the method
ResultSet.relative(1)
steps through the rows of the
result set as expected.
if (rs.first()) { while (!rs.isAfterLast()) { System.out.println( rs.getString("EMP_NO") + " " + rs.getFloat("SALARY")); rs.relative(1); } }
With the new cursor movement methods, it
is easy to see how many rows a scrollable ResultSet
object contains. All that is necessary is to go to the last row of
the result set and get the number of that row. In the following
example, rs will have one row for each employee.
ResultSet rs = stmt.executeQuery( "SELECT LAST_NAME, FIRST_NAME FROM EMPLOYEES"); rs.last(); int numberOfRows = rs.getRow(); System.out.println("XYZ, Inc. has " + numberOfRows + " employees"); rs.beforeFirst(); while (next()) { . . . // retrieve first and last names of each employee }
Though not as convenient, it is also possible to find out how many rows a nonscrollable result set has. The following example shows one way to determine the number of rows.
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM EMPLOYEES"); rs.next(); int count = rs.getInt(1); System.out.println("XYZ, Inc. has " + count + " employees"); ResultSet rs2 = stmt.executeQuery( "SELECT LAST_NAME, FIRST_NAME FROM EMPLOYEES"); while (rs2.next()) { . . . // retrieve first and last names of each employee }
With the scrollable result set, the cursor was just repositioned to start iterating through the same result set to retrieve its data. In the preceding example, however, one query is needed to get the count, and another query is needed to get a result set with the data that is desired. Both queries must, of course, produce result sets of the same size for the count to be accurate.
A second way to determine the number of rows in a forward-only result set is to iterate through the result set, incrementing a variable with each iteration, which is shown in the following example. Because an application can iterate through a forward-only result set just once, the same query needs to be executed twice. In the iteration through the first rs, the number of rows is counted; in the iteration through the second rs, the data is retrieved.
ResultSet rs = stmt.executeQuery( "SELECT LAST_NAME, FIRST_NAME FROM EMPLOYEES"); int count = 0; while (rs.next()) { count++; } System.out.println("Company XYZ has " + count " employees."); rs = stmt.executeQuery( "SELECT LAST_NAME, FIRST_NAME FROM EMPLOYEES"); while (rs.next()) { . . . // retrieve first and last names of each employee }
The ResultSet.getXXX
methods
provide the means for retrieving column values from the current
row. For maximum portability with forward only result sets, values
should be retrieved from left to right, and column values should be
read only once. With scrollable result sets, however, there are no
such restrictions.
Either the column name or the column
number can be used to designate the column from which to retrieve
data. For example, if the second column of a ResultSet
object rs is named TITLE
, and it stores values as
strings, either of the following will retrieve the value stored in
that column:
String s = rs.getString(2); String s = rs.getString("TITLE
");
Note that columns are numbered from left
to right starting with column 1. Also, column names used as input
to getXXX
methods are case insensitive.
The option of using the column name was
provided so that a user who specifies column names in a query can
use those same names as the arguments to getXXX
methods. If, on the other hand, the SELECT
statement
does not specify column names (as in "SELECT * FROM
TABLE1
" or in cases where a column is derived), column
numbers should be used. In such situations, there is no way for the
user to know for sure what the column names are.
In some cases, it is possible for an SQL
query to return a result set that has more than one column with the
same name. If a column name is used as the parameter to a
getXXX
method, getXXX
will return the
value of the first matching column name. Thus, if there are
multiple columns with the same name, one needs to use a column
index to be sure that the correct column value is retrieved. It may
also be slightly more efficient to use column numbers.
If the name of a column is known but not
its index, the method findColumn
can be used to find
the column number.
Information about the columns in a
ResultSet
is available by calling the method
ResultSet.getMetaData
. The
ResultSetMetaData
object returned gives the number,
types, and properties of its ResultSet
object's
columns.
JDBC drivers support type coercion. When
a getXXX
method is invoked, the driver attempts to
convert the underlying data to the type XXX
in the
Java programming language and then returns a suitable value. For
example, if the getXXX
method is
getString
, and the data type of the data in the
underlying database is VARCHAR
, the JDBC Compliant
driver will convert the VARCHAR
value to a
String
object in the Java programming language. That
String
object will be the value returned by
getString
.
The JDBC 2.0 API adds new
ResultSet.getXXX
methods for retrieving the new SQL3
data types. These methods work the same way the getXXX
methods in the JDBC 1.0 API work; that is, they map the SQL3 JDBC
type to a type in the Java programming language and return that
type. For example, the method getClob
retrieves a JDBC
CLOB
value from the database and returns a
Clob
object, which is an instance of the
java.sql.Clob
interface.
The method getObject
will
retrieve any data type. This is possible because
Object
, being the type from which every other object
type in the Java programming language is derived, is the most
generic type. This is especially useful when the underlying data
type is a database-specific type or when a generic application
needs to be able to accept any data type. The method
getObject
, as would be expected from its name, returns
a Java Object
that must be narrowed if it is to be
used as a more specific type. In other words, it must be cast from
its generic Object
type to its more derived type
before it can be used as that derived type. The following code
fragment illustrates using the method getObject
to
retrieve a Struct
value from the column
ADDRESS
in the current row of the
ResultSet
object rs. The Object
that
getObject
returns is narrowed to a Struct
before assigning it to the variable address.
Struct address = (Struct)rs.getObject("ADDRESS");
The method getObject
is not
only the one method capable of retrieving values of any data type
but also the only ResultSet.getXXX
method that does
custom mapping. Therefore, to be custom mapped, a data type has to
be retrieved with the method getObject
. The two SQL
data types that can be custom mapped are the user-defined types,
SQL structured types and DISTINCT
types. A JDBC
DISTINCT
value is normally retrieved with the
getXXX
method appropriate for its underlying type, but
if it has a custom mapping, it must be retrieved by the method
getObject
in order to be custom mapped. A JDBC
STRUCT
can only be retrieved with the method
getObject
, guaranteeing that if there is a custom
mapping for a JDBC STRUCT
value, it will be used.
An "x" indicates that the
getXXX
method may legally be used to retrieve the
given JDBC type.
An "X" indicates that
the getXXX
method is recommended for
retrieving the given JDBC type.
Results sets may have different levels of
functionality. For example, they may be scrollable or
nonscrollable. A scrollable result set has a cursor that moves both
forward and backward and can be moved to a particular row. Also,
result sets may be sensitive or insensitive to changes made while
they are open; that is, they may or may not reflect changes to
column values that are modified in the database. A developer should
always keep in mind the fact that adding capabilities to a
ResultSet
object incurs additional overhead, so it
should be done only as necessary.
Based on the capabilities of
scrollability and sensitivity to changes, there are three types of
result sets available with the JDBC 2.0 core API. The following
constants, defined in the ResultSet
interface, are
used to specify these three types of result sets:
A result set may have different update capabilities. As with
scrollability, making a ResultSet
object updatable
increases overhead and should be done only when necessary. That
said, it is often more convenient to make updates programmatically,
and that can only be done if a result set is made updatable. The
JDBC 2.0 core API offers two update capabilities, specified by the
following constants in the ResultSet
interface:
ResultSet
object
with read-only concurrency needs to set a lock, it uses a read-only
lock. This allow users to read data but not to change it. Because
there is no limit to the number of read-only locks that may be held
on data at one time, there is no limit to the number of concurrent
users unless the DBMS or driver imposes one.To allow a higher level of concurrency, an updatable result set may be implemented so that it uses an optimistic concurrency control scheme. This implementation assumes that conflicts will be rare and avoids using write-only locks, thereby permitting more users concurrent access to data. Before committing any updates, it determines whether a conflict has occurred by comparing rows either by value or by a version number. If there has been an update conflict between two transactions, one of the transactions will be aborted in order to maintain consistency. Optimistic concurrency control implementations can increase concurrency; however, if there are too many conflicts, they may actually reduce performance.
Many DBMSs and drivers are optimized to give the best performance under various circumstances, which means that generally a database programmer is best advised to use their default settings. However, for programmers who may want to fine tune database performance for a particular application, the JDBC 2.0 API provides methods that give hints to the driver for making access to result set data more efficient. These performance hints are exactly that, just hints; a JDBC Compliant driver may choose to ignore them.
The following two hints give the driver suggestions for improving performance:
Statement.setFetchSize
and ResultSet.setFetchSize
. The statement that creates
a ResultSet
object sets the default fetch size for
that ResultSet
object, using the
Statement
method setFetchSize
. The
following code fragment sets the fetch size for the
ResultSet
object rs to 25. Until the fetch size is
changed, any result set created by the Statement
object stmt will automatically have a fetch size of 25.
A result set can, at any time, change its default fetch size by setting a new fetch size with theStatement stmt = con.createStatement(); stmt.setFetchSize(25); ResultSet rs = stmt.executeQuery(SELECT * FROM EMPLOYEES);
ResultSet
version of the method
setFetchSize
. Continuing from the previous code
fragment, the following line of code changes the fetch size of rs
to 50:
Normally the most efficient fetch size is already the default for the driver. The methodrs.setFetchSize(50);
setFetchSize
simply allows a programmer to experiment
to see if a certain fetch size is more efficient than the default
for a particular application. The interface ResultSet
defines the following three
constants for specifying the direction in which to process rows:
FETCH_FORWARD
, FETCH_REVERSE
, and
FETCH_UNKNOWN.
As with the fetch size, there are two
methods for setting the fetch direction, one in the interface
Statement
, and the other in the interface
ResultSet
. The statement that creates the result set
determines the default fetch direction by using the
Statement
method setFetchDirection
. The
following code fragment sets the fetch direction for the
ResultSet
object rs so that it will process rows from
the bottom up. Until the fetch direction is changed, any result set
created by the Statement
object stmt will
automatically have a fetch direction of backward.
A result set can, at any time, change its default fetch direction by setting a new fetch direction with theStatement stmt = con.createStatement(); stmt.setFetchDirection(FETCH_REVERSE); ResultSet rs = stmt.executeQuery(SELECT * FROM EMPLOYEES);
ResultSet
method setFetchDirection
.
Continuing from the previous code fragment, the following line of
code changes the fetch direction of rs to forward.
Thers.setFetchDirection(FETCH_FORWARD);
ResultSet
object rs will hint that the driver
process rows in a forward direction. This hint will be in effect
until the method ResultSet.setFetchDirection
is again
called on rs to change the suggested fetch direction.
As with the fetch size, drivers are commonly optimized to use
the most efficient fetch direction, and changing the default may
actually work against this optimization. The method
setFetchDirection
simply allows a programmer to try to
fine tune an application for even better performance.
A result set is created by executing a
query, and the type of result set depends on the arguments that are
supplied to the Connection
method
createStatement
(or prepareStatement
or
prepareCall
). The following code fragment, which uses
only JDBC 1.0 API, supplies no arguments to the method
createStatement
and thus creates a default
ResultSet
object, one that is forward-only and uses
read-only concurrency.
Connection con = DriverManager.getConnection( "jdbc:my_subprotocol:my_subname"); Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT EMP_NO, SALARY FROM EMPLOYEES");
The variable rs represents a
ResultSet
object that contains the values for the
columns EMP_NO
and SALARY
from every row
in the table EMPLOYEES
. This result set is not
scrollable, so only the method next
can be used to
move the cursor from the top down through the rows of the result
set. The ResultSet
object rs cannot be updated, and
since no performance hints were given, the driver is free to do
whatever it thinks will produce the best performance. The
transaction isolation level was likewise not set, so rs will use
the default transaction isolation level of the underlying database.
(See "Transaction Isolation
Levels" on page 22 for an explanation of transaction
isolation levels.)
The next example uses the new JDBC 2.0
core API to create a scrollable result set that is sensitive to
updates (by specifying
ResultSet.TYPE_SCROLL_SENSITIVE
) and that is updatable
(by specifying ResultSet.CONCUR_UPDATABLE
).
Connection con = DriverManager.getConnection( "jdbc:my_subprotocol:my_subname"); Statement stmt = con.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); stmt.setFetchSize(25); ResultSet rs2 = stmt.executeQuery( "SELECT EMP_NO, SALARY FROM EMPLOYEES");
The variable rs2 contains the same values
as rs, from the previous example, but unlike rs, it is scrollable,
updatable, and sensitive to changes in the underlying table's data.
It also hints that the driver should fetch 25 rows from the
database each time new rows are needed. Each time the
Statement
object stmt is executed, it will create a
result set that is scrollable, is updatable, is sensitive to
changes in its data, and has a fetch size of 25. The result set may
change its fetch size, but it cannot change its type or
concurrency.
As stated previously, there is a cost to making a result set scrollable or updatable, so it is good practice to create result sets with these features only when they are needed.
Because PreparedStatement
and CallableStatement
objects inherit the methods
defined in the Statement
interface, they, too, can
create different types of ResultSet
objects.
The following code fragment creates a
result set using a PreparedStatement
object instead of
a Statement
object. The result set has the same
attributes as in the previous example, except that a transaction
isolation level is set for the connection.
Connection con = DriverManager.getConnection( "jdbc:my_subprotocol:my_subname"); con.setTransactionIsolation(TRANSACTION_READ_COMMITTED); PreparedStatement pstmt = con.prepareStatement( "SELECT EMP_NO, SALARY FROM EMPLOYEES WHERE EMP_NO = ?", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); pstmt.setFetchSize(25); pstmt.setString(1, "1000010"); ResultSet rs3 = pstmt.executeQuery();
The variable rs3 contains the values from
the columns EMP_NO
and SALARY
for the row
where the value for EMP_NO
is 1000010
.
The ResultSet
object rs3 is like rs2 in that it is
scrollable, is updatable, is sensitive to changes in its data, and
hints that the driver should fetch 25 rows at a time from the
database. It is different in that its connection specifies that
dirty reads (reading values before they are committed) will be
prevented. Because no transaction isolation level was set for rs2,
it will by default have the isolation level of the underlying
database.
With the addition of new functionality in the JDBC 2.0 API, it is possible for an application to request features that a DBMS or driver do not support. If the driver does not support scrollable result sets, for example, it may return a forward-only result set. Also, some queries will return a result set that cannot be updated, so requesting an updatable result set would have no effect for those queries. A general rule is that a query should include the primary key as one of the columns it selects, and it should reference only one table.
New methods in the JDBC 2.0 API let an
application discover which result set features a driver supports.
If there is any doubt about whether a feature is supported, it is
advisable to call these methods before requesting the feature. The
following DatabaseMetaData
methods indicate whether a
driver supports a given result set type or a given result set
concurrency:
DatabaseMetaData.supportsResultSetType
- returns a
boolean
indicating whether the driver supports the
given result set type DatabaseMetaData.supportsResultSetConcurrency
-
returns a boolean
indicating whether the driver
supports the given concurrency type in combination with the given
result set type The following ResultSet
methods return the result set type and result set concurrency for
the particular result set on which the method is called:
ResultSet.getType
- returns the type of this
result set ResultSet.getConcurrency
- returns the concurrency
mode of this result set If an application specifies a scrollable
result set and the driver does not support scrolling, the driver
will issue a warning on the Connection
object that
produced the statement and return a result set that is
forward-only. Even if the driver supports scrollable result sets,
it is possible for an application to request a scrollable type that
the driver does not support. In such a case, the driver will issue
an SQLWarning
on the Connection
object
that produced the statement and return a scrollable result set of a
type that it does support, even if it differs from the exact type
requested. For example, if an application requests a
TYPE_SCROLL_SENSITIVE
result set and the driver does
not support that type, it could return a
TYPE_SCROLL_INSENSITIVE
result set if it supports that
type. The driver would also alert the application that it did not
return the exact type requested by issuing an
SQLWarning
on the Connection
object that
produced the statement requesting the unsupported result set
type.
Similarly, if an application specifies an
updatable result set, a driver that does not support updatable
result sets will issue an SQLWarning
on the
Connection
object that produced the statement and
return a read-only result set. If the application requests both an
unsupported result set type and an unsupported concurrency type,
the driver should choose the result set type first.
In some situations, a driver may need to
choose an alternate result set type or concurrency type at
statement execution time. For example, a SELECT
statement that contains a join over multiple tables might produce a
result set that is not updatable. In such a situation, the driver
will issue an SQLWarning
on the
Statement
, PreparedStatement
, or
CallableStatement
object that tried to create the
result set instead of issuing it on the Connection
object. The driver will then choose an appropriate result set type
and/or concurrency type according to the guidelines in the
preceding two paragraphs.
A ResultSet
object may be
updated (have its rows modified, inserted, or deleted)
programmatically if its concurrency type is
CONCUR_UPDATABLE
. The JDBC 2.0 API adds
updateXXX
methods and various other methods to the
ResultSet
interface so that rows can be
programmatically updated in both the ResultSet
object
and the database.
The new updateXXX
methods
make it possible to update values in a result set without using SQL
commands. There is an updateXXX
method for each data
type, and as with the getXXX
and setXXX
methods, the XXX
is a data type in the Java
programming language. As with the setXXX
methods, the
driver converts this data type to an SQL data type before sending
it to the database. So for example, the method
updateBoolean
sends a JDBC BIT
value to
the database, and the method updateCharacterStream
sends a JDBC LONGVARCHAR
value to the database.
The updateXXX
methods take
two parameters, the first to indicate which column is to be
updated, and the second to give the value to assign to the
specified column. As is true with the getXXX
methods,
the column can be specified by giving either its name or its index.
If an application retrieved a value from a result set by using the
column name, it will generally use the column name when it wants to
update that value. Similarly, if the getXXX
method was
given a column index to retrieve a value, the corresponding
updateXXX
method will generally use the column index
to update that value.
Note that the column index used with
ResultSet
methods refers to the column number in the
result set, not the column number in the database table, which
might well be different. (The column numbers will be the same only
in the case where all of a table's columns are selected.) In both
result set tables and database tables, the index for the first
column is 1
, the index for the second column is
2
, and so on.
In the following code fragment, the value
in the third column of the ResultSet
object rs is
retrieved using the method getInt
, and the method
updateInt
is used to update that column value with an
int
value of 88
:
int n = rs.getInt(3); // n contains the value in column 3 of rs . . . rs.updateInt(3, 88); // the value in column 3 of rs is set to 88 int n = rs.getInt(3); // n = 88
If the third column is named
SCORES
, the following lines of code will also update
the third column of the ResultSet
object rs by
assigning it the int
value 88
:
int n = rs.getInt("SCORES"); . . . rs.updateInt("SCORES", 88);
The updateXXX
methods update
a value in the current row of the result set, but they do not
update the value in the underlying database table. It is the method
updateRow
that updates the database. It is very
important that the updateRow
method be called while
the cursor is still on the current row (the row to be updated). In
fact, if an application moves the cursor before it calls
updateRow
, the driver must discard the update, and
neither the result set nor the database will be updated.
An application may explicitly cancel the
updates to a row by calling the method
cancelRowUpdates
. To take effect, it must be called
after the method updateXXX
is called and before the
method updateRow
is called. If
cancelRowUpdates
is called at any other time, it has
no effect.
The following example demonstrates
updating the second and third columns in the fourth row of the
ResultSet
object rs. Since updates affect the current
row, the cursor is first moved to the row to be updated, which in
this case is the fourth row. Next the method
updateString
is
called to change the
value in the second column of rs to 321 Kasten
. The
method updateFloat
changes the value in the third
column of rs to 10101.0
. Finally, the method
updateRow
is called to update the row in the database
that contains the two modified column values.
rs.absolute(4); rs.updateString(2, "321 Kasten"); rs.updateFloat(3, 10101.0f); rs.updateRow();
If the second column is named
ADDRESS
and the third column is named
AMOUNT
, the following code will have exactly the same
effect as the previous example.
rs.absolute(4); rs.updateString("ADDRESS", "321 Kasten"); rs.updateFloat("AMOUNT", 10101.0f); rs.updateRow();
In addition to making updates
programmatically, the JDBC 2.0 core API provides the ability to
send batch updates. The batch update facility operates through a
Statement
object, which is explained in the section
"Sending Batch Updates" on page
39.
The JDBC 2.0 API provides the method
deleteRow
so that a row in a ResultSet
object can be deleted using only methods in the Java programming
language. This method deletes the current row, so before calling
deleteRow
, an application must position the cursor on
the row it wants to delete. Unlike the updateXXX
methods, which affect only a row in the result set, this method
affects both the current row in the result set and the underlying
row in the database. The following two lines of code remove the
first row of the ResultSet
object rs and also delete
the underlying row from the database (which may or may not be the
first row of the database table).
rs.first(); rs.deleteRow();
New rows may be inserted into a result
set table and into the underlying database table using new methods
in the JDBC 2.0 core API. To make this possible, the API defines
the concept of an insert row. This is a special row,
associated with the result set but not part of it, that serves as a
staging area for building the row that is to be inserted. To access
the insert row, an application calls the ResultSet
method moveToInsertRow
, which positions the cursor on
the insert row. Then it calls the appropriate
updateXXX
methods to add column values to the insert
row. When all of the columns of the row to be inserted have been
set, the application calls the method insertRow
. This
method adds the insert row to both the result set and the
underlying database simultaneously. Finally, the application needs
to position the cursor on a row back in the result set.
The following code fragment demonstrates these steps for inserting a row from an application written in the Java programming language.
rs.moveToInsertRow(); rs.updateObject(1, myArray); rs.updateInt(2, 3857); rs.updateString(3, "Mysteries"); rs.insertRow(); rs.first();
Several details deserve attention. First,
it is possible to retrieve values from the insert row using the
ResultSet.getXXX
methods. Until a value has been
assigned to the insert row with an updateXXX
method,
however, its contents are undefined. Therefore, if a
getXXX
method is called after the
moveToInsertRow
method has been called but before an
updateXXX
method has been called, the value it returns
will be undefined.
Second, calling an updateXXX
method on the insert row is different from calling it on a row in
the ResultSet
object. When the cursor is on a row in a
result set, a call to an updateXXX
method changes a
value in the result set. When the cursor is on the insert row, a
call to an updateXXX
method updates a value in the
insert row but does nothing to the result set. In both cases,
though, the updateXXX
method has no effect on the
underlying database.
Third, calling the method
insertRow
, which adds the insert row to both the
result set and database, may throw an SQLException
if
the number of columns in the insert row does not match the number
of columns in the database table. For example, if a column is not
given a value by calling an updateXXX
method, an
SQLException
will be thrown unless that column allows
null values. Also, if the result set is missing a column, that,
too, will cause an SQLException
to be thrown unless
the column allows null values.
Fourth, a result set keeps track of where
its cursor was positioned when the cursor moved to the insert row.
As a result, a call to the method
ResultSet
.moveToCurrentRow
will return
the cursor to the row that was the current row immediately before
the method moveToInsertRow
was called. The other
cursor movement methods also work from the insert row, including
those that use positioning relative to the current row.
Before the JDBC 2.0 API made programmatic updates available in the Java programming language, the only way to change a row that had been fetched with a result set was to use what is called a positioned update. A positioned update is done with SQL commands and requires a named cursor to indicate the result set row in which updates are to be made.
The Statement
interface
provides the method setCursorName
, which allows an
application to specify a cursor name for the cursor associated with
the next result set produced by a statement. This name can then be
used in SQL positioned update or delete statements to identify the
current row in the ResultSet
object generated by the
statement. In order to enable a positioned update or delete on a
result set, the query that produces it must have the following
form:
SELECT . . . FROM . . . WHERE . . . FOR UPDATE . . .
Including the words "FOR
UPDATE
" ensures that the cursor has the proper isolation
level to support an update.
After the method
executeQuery
has been called on the statement, the
cursor name for the resulting ResultSet
object can be
obtained by calling the ResultSet
method
getCursorName
. If a DBMS allows positioned updates or
positioned deletes, the name of the cursor can be supplied as a
parameter to the SQL command for updates or deletes. A
Statement
object other than the one that created the
ResultSet
object must be used for the positioned
update. The following code fragment, in which stmt and stmt2 are
two different Statement
objects, demonstrates the form
for naming a cursor and then using it in an SQL update
statement:
stmt.setCursorName("x"); ResultSet rs = stmt.executeQuery( "SELECT . . . FROM . . . WHERE . . . FOR UPDATE . . .") String cursorName = rs.getCursorName; int updateCount = stmt2.executeUpdate( "UPDATE . . . WHERE CURRENT OF " + cursorName);
Note that just because the method
getCursorName
has been invoked on a
ResultSet
object does not necessarily mean that it can
be updated using the ResultSet.updateXXX
methods
available in the JDBC 2.0 core API. In order to update a
ResultSet
object using the updateXXX
methods, the executeQuery
statement that produces the
result set must include the specification
CONCUR_UPDATABLE
. Positioned updates, however, are
possible for a result set created without this specification if all
the proper steps are taken: (1) a cursor is named, (2) the SQL
query that produces the result set is of the form SELECT . . . FROM
. . . WHERE . . . FOR UPDATE . . ., and (3) the SQL update
statement is of the form UPDATE . . . WHERE CURRENT OF
<cursorName>
.
Not all DBMSs support positioned updates.
To verify that a DBMS supports positioned updates, an application
can call the DatabaseMetaData
methods
supportsPositionedDelete
and
supportsPositionedUpdate
to discover whether a
particular connection supports these operations. When they are
supported, the DBMS/driver must ensure that rows selected are
properly locked so that positioned updates do not result in update
anomalies or other concurrency problems.
Some queries will produce result sets that cannot be updated no matter what the result set type. For example, a query that does not select the primary key column might generate a result set that cannot be updated. Because of differences in database implementations, the JDBC 2.0 core API does not specify an exact set of SQL queries that must yield updatable result sets. Instead it defines a set of criteria that should generally produce updatable result sets for JDBC Compliant drivers that support updatability. If queries adhere to the following guidelines, a developer can generally expect that they will produce updatable result sets:
GROUP
BY
clause If inserts are to be performed on the result set, an SQL query should satisfy conditions one through three plus the following three additional conditions:
The fourth and fifth conditions are necessary because a row to be inserted into a table must have a value for each column in the table unless the column accepts null values or default values. If the result set on which insertion operations are to be performed does not contain every column that requires a value, the insertion will fail.
Result sets created by means other than
the execution of a query, such as those returned by several methods
in the DatabaseMetaData
interface, are not scrollable
or updatable, nor are they required to be.
Two new interfaces in the JDBC 2.0 core
API, Blob
and Clob
, are the mapping of
the SQL3 data types BLOB
(Binary Large Object) and
CLOB
(Character LargeObject) in the Java programming
language. With the availability of these data types, databases will
undoubtedly start using them to store very large binary or
character objects. If this is the case, the ResultSet
methods getBlob
and getClob
should be
used to retrieve them.
Using only the JDBC 1.0 API, a
ResultSet
object still makes it possible to retrieve
arbitrarily large LONGVARBINARY
or
LONGVARCHAR
data. The methods getBytes
and getString
return data as one large chunk (up to
the limits imposed by the return value of
Statement.getMaxFieldSize
). It is possible to retrieve
this large chunk of data in smaller, fixed-size chunks. This is
done by having the ResultSet
class return
java.io.Input
streams from which data can be read in
chunks. Note that these streams must be accessed immediately
because they will be closed automatically when the next
getXXX
method is called on the ResultSet
object. (This behavior is not a limitation of the JDBC API but
rather a constraint on large blob access imposed by the underlying
implementations in some database -systems.)
The JDBC 1.0 API has three separate methods for getting streams, each with a different return value:
getBinaryStream
- returns a stream that simply
provides the raw bytes from the database without any
conversiongetAsciiStream
- returns a stream that provides
one-byte ASCII characters. This method can be more efficient for a
DBMS that stores characters in ASCII format.getUnicodeStream
- returns a stream that provides
two-byte Unicode characters. This method, though still available,
has been deprecated in favor of the new method
getCharacterStream
. (See below.)The following method for retrieving streams of both ASCII and Unicode characters is new in the JDBC 2.0 core API:
getCharacterStream
- returns a
java.io.Reader
object that provides Unicode
characters. No matter how a DBMS stores characters, the driver will
return them as a stream of Unicode characters. Note that the stream returned by
getAsciiStream
returns a stream of bytes in which each
byte is an ASCII character. This differs from
getCharacterStream
, which returns a stream of two-byte
Unicode characters. The method getCharacterStream
can
be used for both ASCII and Unicode characters because the driver
will convert ASCII characters to Unicode before it returns a
Reader
object. If you must use
getUnicodeStream
because your DBMS and driver do not
support the JDBC 2.0 API, note also that JDBC Unicode streams
return big-endian data; that is, they expect data with the high
byte first and the low byte second. This conforms to the standard
endian defined by the Java programming language, which is important
if a program is to be portable. Refer to The Javatm
Virtual Machine Specification, by Tim Lindholm and Frank
Yellin, for more detailed information about big-endian order.
The following code fragment demonstrates
how to use the getAsciiStream
method.
java.sql.Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT x FROM Table2"); // Now retrieve the column 1 results in 4 K chunks: byte [] buff = new byte[4096]; while (rs.next()) { java.io.InputStream fin = rs.getAsciiStream(1); for (;;) { int size = fin.read(buff); if (size == -1) { // at end of stream break; } // Send the newly-filled buffer to some ASCII output stream output.write(buff, 0, size); } }
To determine if a given result value is
JDBC NULL
, one must first read the column and then use
the method ResultSet.wasNull
. This is true because a
JDBC NULL
retrieved by one of the
ResultSet.getXXX
methods may be converted to either
null
, 0
, or false
, depending
on the type of the value.
The following list shows which values are
returned by the various getXXX
methods when they have
retrieved a JDBC NULL
.
null
-for those getXXX
methods that
return objects in the Java programming language
(getString
, getBigDecimal
,
getBytes
, getDate
, getTime
,
getTime-stamp
, getAsciiStream
,
getCharacterStream
, getUnicodeStream
,
getBinary-Stream
, getObject
,
getArray
, getBlob
, getClob
,
and getRef
) 0
(zero)-for getByte
,
getShort
, getInt
, getLong
,
getFloat
, and getDouble
false
-for getBoolean
For example, if the method
getInt
returns 0
from a column that
allows null
values, an application cannot know for
sure whether the value in the database was 0
or
NULL
until it calls the method wasNull
,
as shown in the following code fragment, where rs is a
ResultSet
object.
int n = rs.getInt(3); boolean b = rs.wasNull();
If b is true
, the value
stored in the third column of the current row of rs is JDBC
NULL
. The method wasNull
checks only the
last value retrieved, so to determine whether n was
NULL
, wasNull
had to be called before
another getXXX
method was invoked.
Normally, nothing needs to be done to
close a ResultSet
object; it is automatically closed
by the Statement
object that generated it when that
Statement
object is closed, is re-executed, or is used
to retrieve the next result from a sequence of multiple results.
The method close
is provided so that a
ResultSet
object can be closed explicitly, thereby
immediately releasing the resources held by the
ResultSet
object. This could be necessary when several
statements are being used and the automatic close does not occur
soon enough to prevent database resource conflicts.
Drivers that are JDBC Compliant should normally support scrollable result sets, but they are not required to do so. The intent is for JDBC drivers to implement scrollable result sets using the support provided by the underlying database systems. If the DBMS does not provide support for scrollability, then the driver may omit this feature.
Making scrollability optional is not
meant to encourage omitting it. It is simply meant to minimize the
complexity of implementing JDBC drivers for data sources that do
not support scrollability. Indeed, the recommended alternative is
for a driver to implement scrollability as a layer on top of the
DBMS. One way to do this is to implement a result set as a rowset.
The RowSet
interface, which provides methods for doing
this, is part of the JDBC Standard Extension API.
Java Technology |
Copyright © 1993, 2010, Oracle and/or its affiliates. All rights reserved. |