RowSet
object is a container for tabular data, encapsulating a set of zero or more
rows that have been retrieved from a data source. In a basic implementation of the
RowSet
interface, the rows are retrieved from a JDBC data source, but a rowset may
be customized so that its data can also be from a spreadsheet, a flat file, or any other
data source with a tabular format. A RowSet
object extends the ResultSet
interface,
which means that it can be scrollable, can be updatable, and can do anything a
ResultSet
object can do. The features of a RowSet
object, which are summarized in
this introductory section, will be explained in more detail in later sections.
A RowSet
object differs from a ResultSet
object in that it is a JavaBeansTM
component. Thus, it has a set of JavaBeans properties and follows the JavaBeans
event model. A RowSet
object's properties allow it to establish its own database
connection and to execute its own query in order to fill itself with data. A rowset
may be disconnected, that is, function without maintaining an open connection to
a data source the whole time it is in use. In addition, a rowset can be serialized,
which means that it can be sent to a remote object over a network.
In general, the JDBC API can be divided into two categories, the RowSet
portion
and the driver portion. RowSet
and its supporting interfaces are intended to be
implemented using the rest of the JDBC API. In other words, a class that implements
the RowSet
interface is a layer of software that is said to execute "on top" of
a JDBC driver. Unlike other JDBC objects, a RowSet
object contains within itself
the means to operate without a driver and without being connected to a data
source.
The release of J2SE 5.0 introduced a third category. In addition to the
RowSet
API and the driver, there are five standard implementations of the RowSet
interface. These implementations provide a set of interfaces that extend the basic
RowSet
interface plus reference implementations for each of them. There is no
requirement to use these implementations, but by using them, developers can be
sure that their implementations follow the JDBC API in event handling, cursor
manipulation, and other operations. The standard implementations are discussed
more fully in "Standard Implementations," on page 808.
The RowSet
interface provides a basic set of methods common to all rowsets,
which this section describes. All RowSet
objects are JavaBeans components; therefore,
the RowSet
interface has methods for adding and removing an event listener,
and it has getter amd setter methods for all of its properties. Many of these properties
support setting up a connection or executing a command. A rowset uses a connection
with a data source in order to execute a query and produce a result set
from which it will get its data. It may also use a connection to write modified data
back to the data source. In addition, the RowSet
interface has methods (one for
each data type) for setting the values of input parameters, if any, in a RowSet
object's command string. In the JDBC RowSet
Implementations specification,
these basic methods, defined in the RowSet
interface, are provided in the BaseRowSet
abstract class, which is discussed later.
Five other interfaces and one class work together with the RowSet
interface
behind the scenes. The class RowSetEvent
and the interface RowSetListener
support
the JavaBeans event model. When a RowSet
object's cursor moves or its data
is modified, it will invoke the RowSetListener
method corresponding to the event,
providing it with a RowSetEvent
object that identifies itself as the source of the
event. Implementations are free to write extensions that add other RowSet
events if
they are needed.
A component that wants to be notified of the events that occur in a RowSet
object will implement the RowSetListener
interface and be registered with the
RowSet
object. Such a component, called a listener, is typically a GUI (graphical
user interface) component, such as a table or bar chart, that is displaying the
RowSet
object's data. Because a listener is notified every time an event occurs in
the rowset, it can keep its cursor position and data consistent with that of the
rowset.
The interfaces RowSetInternal
, RowSetReader
, and RowSetWriter
support the
rowset
reader/writer facility. A reader, an instance of a class that implements the
RowSetReader
interface, reads data and inserts it into a rowset. A writer, an
instance of a class that implements the RowSetWriter
interface, writes modified
data back to the data source from which a rowset's data was retrieved.
The RowSetInternal
interface provides additional methods for a reader or
writer to use to manipulate the rowset's internal state. For example, a rowset can
keep track of its original values, and RowSetInternal
methods allow the writer to
see if the corresponding data in the data source has been changed by someone
else. In addition, RowSetInternal
methods make it possible to retrieve the input
parameters that were set for a rowset's command string and to retrieve the connection
that was passed to it, if there is one. Finally, RowSetInternal
methods allow a
reader to set a new RowSetMetaData
object, which describes to the rowset the rows
that the reader will insert into it. The name of the interface is RowSetInternal
for
good reason. Its methods are used internally; an application does not call these
methods directly.
Rowsets may be either connected or disconnected. A connected RowSet
object
maintains a connection to its data source the entire time it is in use, whereas a disconnected
rowset is connected to its data source only while it is reading data from
the data source or writing data to it. While the rowset is disconnected, it does not
need a JDBC driver or the full implementation of the JDBC API. This makes it
very lean and therefore an ideal container for sending a set of data to a thin client.
The client can, if it chooses, make updates to the data and send the rowset back to
the application server. On the server, the disconnected RowSet
object uses its
reader to make a connection to the data source and write data back to it. Exactly
how this is done depends on how the reader is implemented. Typically, the reader
delegates making a connection and reading data to the JDBC driver.
RowSet
event model makes it possible for a Java object, or component, to be
notified about events generated by a RowSet
object. Setting up the notification mechanism
involves both the component to be notified and the RowSet
object itself. First,
each component that wants to be notified of events must implement the RowSetListener
interface. Then the RowSet
object must register each component by adding it
to its list of components that are to be notified of events. At this point, such a component
is a listener, an instance of a class that implements the RowSetListener
methods and is registered with a RowSet
object.
Three kinds of events can occur in a RowSet
object: its cursor can move, one
of its rows can change (be inserted, deleted, or updated), or its entire contents can
be changed. The RowSetListener
methods cursorMoved
, rowChanged
, and
rowSetChanged
correspond to these events. When an event occurs, the rowset will
create a RowSetEvent
object that identifies itself as the source of the event. The
appropriate RowSetListener
method will be invoked on each listener, with the
RowSetEvent
object being passed to the method. This will inform all of the
rowset's listeners about the event.
For example, if a pie chart component, pieChart
, wants to display the data in
the RowSet
object rset
, pieChart
must implement the RowSetListener
methods
cursorMoved
, rowChanged
, and rowSetChanged
. The implementations of these
methods specify what pieChart
will do in response to an event on rset
. After
implementing these methods, pieChart
can be registered with rset
. When pieChart
is added as a listener to rset
, it will be notified when an event occurs on
rset
by having the appropriate method invoked with a RowSetEvent
object as its
parameter. The listener pieChart
can then update itself to reflect the current data
and cursor position of rset
. If pieChart
does not need to reflect one of the events
on rset
, it can implement the RowSetListener
method for that event so that it
does nothing. For instance, if pieChart
does not need to show the current cursor
position in rset
, it can have the method cursorMoved
do nothing.
Any number of components may be listeners for a given RowSet
object. If, for
example, the bar graph component barGraph
is also displaying the data in rset
, it
can become a listener by implementing the RowSetListener
methods and then
being registered with rset
. The following lines of code register the two components
pieChart
and barGraph
as listeners with rset
.
rset.addRowSetListener(pieChart); rset.addRowSetListener(barGraph);Removing a listener is done in a similar fashion with the method
RowSet.removeListener
.
rset.removeRowSetListener(pieChart); rset.removeRowSetListener(barGraph);The code for setting up the listeners for a rowset is often generated by a tool, which means that an applications programmer only needs to specify a rowset and the components that are to be notified when an event occurs on that rowset. After the listeners are set up, the processing of an event is done largely behind the scenes. For example, if an application updates a row in
rset
, rset
will internally
create a RowSetEvent
object and pass it to the rowChanged
methods implemented
by pieChart
and barGraph
. The listeners will know where the event occurred
because the RowSetEvent
object passed to rowChanged
is initialized with rset
, the
RowSet
object that is the source of the event. The components pieChart
and barGraph
will update their displays of the row according to their own implementations
of the RowSetListener.rowChanged
method.
In the following code fragment, pieChart
and barGraph
are registered as listeners
with the RowSet
object rset
. After rset
fills itself with new data by calling
the method execute
, event notification takes place behind the scenes. (The
method execute
will be explained in detail in the section "Executing a Command
," on page 805.) As the first step in the event notification process, the
RowSetEvent
object rsetEvent
is created and initialized with rset
. Next the pieChart
and barGraph
versions of the method rowSetChanged
are called with
rsetEvent
as their arguments. This tells pieChart
and barGraph
that all the data in
rset
has changed, and each listener will carry out its own implementation of the
method rowSetChanged
.
rset.addRowSetListener(pieChart); rset.addRowSetListener(barGraph); . . . rset.execute(); // The following methods will be invoked behind the scenes: RowSetEvent rsetEvent = new RowSetEvent(this); pieChart.rowSetChanged(rsetEvent); barGraph.rowSetChanged(rsetEvent);
RowSet
interface provides a set of JavaBeans properties so that a RowSet
instance can be configured to connect to a data source and retrieve a set of rows.
Some properties may not be required, depending on particular implementations. For
example, either a URL or a data source name is required for establishing a connection,
so if one property is set, the other one is optional. If both are set, the one set
more recently is used. If data for a rowset is being retrieved from a non-SQL data
source that does not support commands, such as a spreadsheet, the command property
does not need to be set. Setting some properties is optional if the default is
already the desired property. For example, escape processing is on by default, so an
application does not need to set escape processing unless it wants to disable it.
(Escape processing is explained in "SQL Escape Syntax in Statements," on
page 958.)
The following list gives the getter and setter methods defined on the RowSet
interface for retrieving and setting a RowSet
object's properties. The two exceptions
are the methods getConcurrency
and getType
, which are inherited from the
ResultSet
interface rather than being defined in the RowSet
interface.
getCommand |
setCommand |
---|---|
ResultSet.getConcurrency | setConcurrency |
getDataSourceName | setDataSourceName |
getEscapeProcessing | setEscapeProcessing |
getMaxFieldSize | setMaxFieldSize |
getMaxRows | setMaxRows |
getPassword | setPassword |
getQueryTimeout | setQueryTimeout |
getTransactionIsolation | setTransactionIsolation |
ResultSet.getType | setType |
getTypeMap | setTypeMap |
getUrl | setUrl |
getUsername | setUsername |
In addition, the standard JDBC RowSet
implementations may have various properties
that are specific to them.
The following code fragment, in which rset
is a RowSet
object, sets properties
that are typically required for establishing a connection with a data source using a
DataSource
object.
rset.setDataSourceName("jdbc/logicalDataSourceName"); rset.setUsername("cervantes"); rset.setPassword("secret");Note that
jdbc/logicalDataSourceName
is the name that has been registered with
a JNDI (Java Naming and Directory Interface) naming service. When an application
gives the naming service the logical name, it will return the DataSource
object
that has been bound to the logical name. "Using JNDI," on page 568, explains using
DataSource
objects and the JNDI API. Using a DataSource
object instead of
hardcoding connection information makes code more portable and makes maintaining
it much easier. If the host machine or port number of a data source changes,
for example, only the properties of the DataSource
object entered in the JNDI naming
service need to be updated, not every application that gets a connection to that
data source.
A RowSet
object also has methods for setting properties that affect command
execution. For example, as a sampling of these methods, the following code fragment
sets twenty seconds as the longest a driver will wait for a statement to execute,
sets 1024 as the largest number of rows rset
may contain, and specifies that
rset
will be allowed to read only data from committed transactions.
rset.setQueryTimeout(20); rset.setMaxRows(1024); rset.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);The type and concurrency may also be set for a
RowSet
object, as shown in the
following lines of code.
rset.setType(ResultSet.TYPE_SCROLL_INSENSITIVE); rset.setConcurrency(ResultSet.CONCUR_UPDATABLE);The first line sets
rset
to be scrollable but not sensitive to the updates made while
it is open. A RowSet
object that maintains a continuously open connection with a
data source may be TYPE_SCROLL_SENSITIVE
, but one that does not is incapable of
being sensitive to changes made by other objects or transactions. The second line
of code sets rset
to be updatable, meaning that it can modify its data.
Note that a RowSet
object may be scrollable even if it uses a driver that does
not support scrollable result sets. In fact, a rowset may often be used in place of a
regular result set as a way of getting a scrollable result set. This is discussed in
more detail in the section "Traversing a RowSet
Object," on page 804.
An application fills a RowSet
instance with data by executing the RowSet
object's command string. This string must be a query that, when executed, will
produce a result set from which the RowSet
object will get its data. The method
setCommand
sets the String
object supplied to it as the command that will be executed
when the method execute
is invoked. For example, the following line of
code sets the command string to a query that selects the name and salary from
every row in the table EMPLOYEES
.
rset.setCommand("SELECT NAME, SALARY FROM EMPLOYEES");
With the preceding command string set, after rset
invokes its execute
method, it
will contain exactly the same data as the result set that the query produces (one row
for each row in the table EMPLOYEES
, with each row containing a name and a salary).
PreparedSatement
object. Note that the parameters must be input parameters
and not output parameters.
A newly created RowSet
object exists but has no data until it is populated with
a call to the method execute
or populate
. In order to use the method execute
, the
necessary properties must be set, and values for any placeholder parameters must
be set. Parameter values can be set at run time, which, for example, allows an
application to be interactive and accept user input.
The RowSet
interface, like the PreparedStatement
interface, has setter methods
for setting the value of input parameters. There is a setter method for each
data type, including the SQL99 data types. The following code fragment sets the
command string and then sets its two input parameters with values. Assuming that
the column DEPT
stores values of type VARCHAR
, the method setString
is used to
set both parameters because that is the appropriate method for setting VARCHAR
values.
rset.setCommand(
"SELECT NAME, SALARY FROM EMPLOYEES WHERE DEPT = ? OR DEPT = ?");
rset.setString(1, "SALES");
rset.setString(2, "MARKETING");
After this command is executed, rset
will contain the names and salaries of the
employees in the sales and marketing departments.
Any parameters in a RowSet
object's command string must be set with values
before the command is executed. When a rowset is disconnected, the parameters
that have been set are used by the reader's readData
method, which is invoked
internally by the method execute
. A rowset stores these parameter values in an
internal hashtable, and the readData
method retrieves them with a call to the
rowset's RowSetInternal.getParams
method.
javax.sql.RowSet
interface extends the java.sql.ResultSet
interface, so
moving the cursor in a scrollable RowSet
object is exactly the same as moving a cursor
in a scrollable ResultSet
object. A RowSet
object inherits all of the ResultSet
methods, so it is really a result set with added features that allow it to function as a
JavaBeans component. Most components that use instances of RowSet
are likely to
treat them as ResultSet
objects.
Even though the methods for moving a RowSet
object's cursor are identical to
those of a ResultSet
object from the user's point of view, a RowSet
object's implementation
of these methods is different. A RowSet
object needs to let the listeners
registered with it know about each movement of its cursor. Consequently, the cursor
movement methods for a RowSet
object are implemented to trigger the internal
event notification process. For example, when the method next
is called, its
implementation will create a
RowSetEvent
object and call each listener's cursorMoved
method, supplying the RowSetEvent
object as the parameter. The listener
uses the RowSetEvent
object to find out in which RowSet
object the cursor has
moved and then invokes its implementation of the method cursorMoved
. The
implementation could do nothing, or it could, for example, call the method
ResultSet.getRow
to get the cursor's current position and update the listener's
display of that row's data.
To demonstrate that cursor movements are the same in RowSet
and ResultSet
,
the following code fragment iterates forward through the RowSet
object rset
and
prints out the two values retrieved from each row.
rset.beforeFirst(); while (rset.next()) { System.out.println(rset.getString(1) + " " + rset.getFloat(2)); }Other cursor movements are also identical to those in the
ResultSet
interface. .
RowSet
interface provides the method execute
, which is invoked to fill a RowSet
object with data. There can be many variations in the implementation of this
method, and subtypes may define additional methods for populating themselves
with data. The execute
method makes use of rowset properties and will throw an
SQLException
if the necessary properties have not been set. Standard properties
have been defined; however, additional properties depend on each particular RowSet
implementation, so application writers should check the documentation for the
implementation they are using.
A disconnected rowset needs a reader (an object that implements the RowSetReader
interface) and a writer (an object that implements the RowSetWriter
interface)
. In the JDBC RowSet
Implementations, a reader and writer are encapsulated
in the the SyncProvider
class, which is part of the javax.sql.rowset.spi
package.
This package, known as the SPI (Service Provider Interface), is explained
later. The SPI makes it much easier to implement and deploy a reader and writer.
The two reference implementations, RIOptimisticProvider
and RIXmlProvider
,
provide immediate implementations.
A disconnected rowset must also implement the RowSetInternal
interface to
make additional access to its internal state available to the reader and writer. With
its reader/writer framework in place, a rowset's execute
method is able to delegate
tasks to its reader and writer components.
With the addition of the standard RowSet
implementations, developers will
find it much easier to implement the reader/writer facilities. They can leverage the
reader/writer facilities that are already included in these standard implementations
by simply incorporating them into their own implementations.
In a typical implementation, a disconnected rowset's execute
method will
invoke the reader's readData
method to accomplish the job of reading new data
into the rowset. Generally, after clearing the rowset of its current contents, the
readData
method will get the properties it needs and establish a connection with
the data source. If there are any parameters to be set, readData
retrieves them
from the rowset and sets them appropriately in the rowset's command string. Then
the readData
method executes the command string to produce a result set. Finally,
readData
populates the rowset with the data from the result set and sets that data
as the original values.
The reader's readData
method may also be implemented to set the rowset's
metadata. One of the many possible implementations is to have the readData
method create a RowSetMetaData
object and set it with information about the columns
in the data source that is about to be read. The readData
method next sets
the new RowSetMetaData
object to be the one associated with the rowset. The
rowset can then use the RowSetMetaData
object to see the format for the data that
will be read into it.
When the rowset's command string is executed, all of a rowset's listeners need
to be notified so that they can take the appropriate action. The execute
method
will invoke the rowSetChanged
method on each listener, supplying rowSetChanged
with a newly created RowSetEvent
object that identifies the RowSet
object in which
the event occurred. As is true of most of what the method execute
does, the notification
of listeners is invisible to the application programmer using a rowset.
One more task for the execute
method is that of setting the "original" values
maintained by the rowset. These are the values, returned by the method RowSetInternal.getOriginal
, that existed immediately before the most recent update. A
RowSet
object's original values may be the values it got from the data source, but
this is not necessarily true. The first time a RowSet
object's data is synchronized
with its data source, its original values will be the same as the values it originally
got from the data source. However, if some of a RowSet
object's values are modified
a second time, the original values will be the values that existed after the first
modifications, not those from the data source. The point of keeping track of a
RowSet
object's original values is to be able to check whether the corresponding
values in the data source have been changed.
If a SyncProvider
object is implemented to check for conflicts, it compares
the original values with the ones in the data source. If the rowset's original values
(pre-modification values) and the underlying data source's values are not the
same, there will be a conflict if the RowSet
object's modified values are written to
the data source. If the values are the same, however, there is no conflict. Depending
on the synchronization model being used, a SyncProvider
object may or may
not write the new rowset values to the data source when there is a conflict.
If the SyncProvider
implementation does not write new values to the underlying
data source, the execute
method will reset the original values back to the values
currently in the rowset. Then the next time execute
is called, which changes
all of the values in the rowset, the SyncProvider
object can retrieve these values to
compare with those in the underlying data source to see if there is a conflict.
In summary, when an application invokes the execute
method, many operations
take place behind the scenes. When the rowset is disconnected, the following
take place: the contents of the rowset are replaced with new data, any listeners
using the rowset's data are notified, the rowset's metadata is updated, and the
rowset's original values are set to the current data values. For a connected rowset,
the execute
method generally just populates the rowset with new data and notifies
the listeners of the event. There is no possibility of a conflict because changes
made to a connected RowSet
object are also made to the data source.
RowSet
object maintains a set of metadata about the columns it contains. Being
derived from ResultSet
, a RowSet
object's metadata can be retrieved with ResultSetMetaData
methods in the same way that a ResultSet
object's metadata can. For
instance, the following code fragment creates a RowSetMetaData
object for the
RowSet
object rset
and finds out how many columns rset
contains. Note that the
method getMetaData
is a ResultSet
method that returns a ResultSetMetaData
object, so it must be cast to a RowSetMetaData
object before it can be assigned to
rsetmd
.
RowSetMetaData rsetmd = (RowSetMetaData)rset.getMetaData(); int columnCount = rsetmd.getColumnCount();The variable
rsetmd
contains information about the columns in the RowSet
rset
. Any of the RowSetMetaData
methods can be invoked on rsetmd
to retrieve
the information that rsetmd
contains.
The interface RowSetMetaData
defines setter
methods corresponding to each
of the getter methods defined in ResultSetMetaData
(except that there are no
methods for setting the class name for a column or for setting whether the column
is read-only, possibly writable, or definitely writable). The RowSetMetaData
setter
methods are called by a reader after it has read new data into a rowset and created
a new RowSetMetaData
object for describing the RowSet
object's columns. The following
code shows what a reader might do behind the scenes. It creates the new
RowSetMetaData
object rowsetmd
for the RowSet
object rowset
, sets the information
for the columns, and finally calls the RowSetInternal
method setMetaData
to
set rowsetmd
as the metadata for rowset
.
rowset.execute(); // ... as part of its implementation, execute calls readData reader.readData((RowSetInternal)this); // ... as part of the implementation of readData, the reader for // the rowset would do something like the following to update the // metadata for the rowset RowSetMetaData rowsetmd = new ...; // create an instance of a class // that implements RowSetMetaData rowsetmd.setColumnCount(3); rowsetmd.setColumnType(1, Types.INTEGER); rowsetmd.setColumnType(2, Types.VARCHAR); rowsetmd.setColumnType(3, Types.BLOB); // ... set other column information rowset.setMetaData(rowsetmd);
RowSet
interface
with the release of J2SE 5.0. These implementations are being provided as
an aid for those who want to write their own implementations. The RowSet
interface
may be implemented in any number of ways to serve any number of different purposes,
and anyone may implement it. The expectation is, however, that RowSet
implementations will be written mostly by driver vendors, who may include their
implementations as part of their JDBC products. Implementors are free to use the
reference implementations just as they are, to build on them, or to write their implementations
completely on their own.
The standard implementations consist of two parts, the interfaces and the reference
implementations. The interfaces are in the javax.sql.rowset
package; the
implementations are in the com.sun.rowset
package. They were developed with
input from experts in the database field through the Java Community Process as
JSR 114. The goal was to standardize key rowset functionality so that developers
can leverage it in their own implementations. The standard RowSet
implementations
are:
RowSet
implementations extend the abstract class BaseRowSet
and implement the appropriate interface (JdbcRowSet
, CachedRowSet
, WebRowSet
,
FilteredRowSet
, or JoinRowSet
). Note that the BaseRowSet
class provides a base
implementation of the common functionality for all RowSet
objects, which developers
may choose to use or not. The BaseRowSet
class includes the following:
RowSet
object's command property
RowSet
implementations have the following default
values:
RowSet
object's
command
BINARY
, VARBINARY
,
LONGVARBINARY
, CHAR
, VARCHAR
, or LONGVARCHAR
may contain
null
Hashtable
object for storing the values set for the placeholder
parameters in the RowSet
object's command
As stated previously, RowSet
objects may be connected or disconnected. The
RowSet
implementations fall into the following categories:
JdbcRowSet
The CachedRowSet
interface provides the methods that a disconnected RowSet
object needs. In the standard implementations, a disconnected RowSet
extends the
BaseRowSet
class and implements the CachedRowSet
interface. In addition, the
JoinRowSet
implementation implements the JoinRowSet
interface, the FilteredRowSet
implementation implements the FilteredRowSet
interface, and the
WebRowSet
implementation implements the WebRowSet
interface.
JdbcRowSet
object (an instance of the standard implementation of the JdbcRowSet
interface) is, like all rowsets, a container for a set of rows. The source of these rows
is always a ResultSet
object because a JdbcRowSet
object is a connected RowSet
object. In other words, it always maintains a connection with a DBMS via a JDBC
driver. Note that other implementations may use any tabular data, such as a flat file
or a spreadsheet, as their source of data if their reader and writer facilities are appropriately
implemented.
A JdbcRowSet
object has many uses. Probably the most common use is to
make a ResultSet
object scrollable and thereby make better use of legacy drivers
that do not support scrolling. A JdbcRowSet
object's rows of data (and those in any
RowSet
object) are identical to those in the ResultSet
object that is the result of
executing the rowset's command. Therefore, if the rowset is scrollable, it is the
equivalent of having a scrollable ResultSet
object even if the ResultSet
object
itself is not scrollable.
Another common use is to make the driver or a ResultSet
object a JavaBeans
component. Like all RowSet
objects, a JdbcRowSet
object is a JavaBeans component.
By being continuously connected to a driver, it serves as a wrapper for the
driver, which effectively makes the driver a JavaBeans component. This means
that a driver presented as a JdbcRowSet
object can be one of the Beans that a tool
makes available for composing an application. Being continuously connected also
means that a JdbcRowSet
object is able to serve as a wrapper for its ResultSet
object. It can take calls invoked on it and, in turn, call them on its ResultSet
object. As a consequence, the ResultSet
object can be, for example, a component
in a GUI application that uses Swing
technology.
The following code fragment illustrates creating a JdbcRowSet
object, setting
its properties, and executing the command string in its command
property. The JdbcRowSet
implementation provides a default constructor, but being a JavaBeans
component, a JdbcRowSet
implementation will probably most often be created by
a visual JavaBeans development tool.
JdbcRowSet jrs = new JdbcRowSetImpl(); jrs.setCommand("SELECT * FROM TITLES); jrs.setURL("jdbc:myDriver:myAttribute"); jrs.setUsername("cervantes"); jrs.setPassword("sancho"); jrs.execute();At this point,
jrs
contains all of the data in the table TITLES
because the ResultSet
object generated by jrs
's command contains all of the data in the table TITLES
.
From this point on, the code can simply use ResultSet
methods because it is
effectively operating on a ResultSet
object. It can navigate the rows in jrs
,
retrieve column values, update column values, insert new rows, and so on. For
example, the next two lines of code go to the second row and retrieve the value in
the first column using ResultSet
methods.
jrs.absolute(2); String title = jrs.getString(1);
CachedRowSet
interface (CachedRowSetImpl
in
the package com.sun.rowset
) provides a container for a set of rows that is being
cached in memory outside of a data source. It is disconnected, serializable, updatable,
and scrollable. Because a CachedRowSet
object caches its own data, it does not
need to maintain an open connection with a data source and is disconnected from its
data source except when it is reading or writing data. But because it stores its rows
in memory, a CachedRowSet
object is not appropriate for storing extremely large
data sets. However, to accommodate larger amounts of data, a CachedRowSet
object
may page in data, reading only a specified number of rows at a time.
A CachedRowSet
object can populate itself with data from a tabular data
source, and because it is updatable, it can also modify its data. As with all RowSet
objects, in addition to getting data in, it can get data out, propagating its modifications
back to the underlying data source.
CachedRowSet
object is especially well suited
for use with a thin client. Not being continually connected to its data source, it does
not require the presence of a JDBC driver and can therefore be much more lightweight
than a ResultSet
object or a connected RowSet
object.
One of the major uses for a CachedRowSet
instance is to pass tabular data
between different components of a distributed application, such as EnterpriseJavaBeans
TM (EJBTM) components running in an application server. The server can use
the JDBC API to retrieve a set of rows from a database and then use a CachedRowSet
object to send the data over the network to, for example, a thin client running
in a web browser.
Another use for a CachedRowSet
object is to provide scrolling and updating
capabilities to a ResultSet
object that does not itself have this functionality. For
example, as with a JdbcRowSet
object, an application can create a CachedRowSet
object initialized with the data from a ResultSet
object and then operate on the
rowset instead of the result set. With the rowset set to be scrollable and updatable,
the application can move the cursor, make updates, and then propagate the updates
back to the data source.
The following code fragment populates the CachedRowSet
object crset
with
the data from the ResultSet
object rs
.
ResultSet rs = stmt.executeQuery("SELECT * FROM AUTHORS"); CachedRowSet crset = new CachedRowSetImpl(); crset.populate(rs);Once the rowset is populated, an application can pass it across the network to be manipulated by distributed components, or it can operate on
crset
instead of rs
to
gain scrollability or updatability.
The CachedRowSet
implementation uses the base API from the BaseRowSet
abstract class and the CachedRowSet
interface, which adds methods for the additional
functionality it needs. For example, it needs a way to connect to a data
source and read data from it. If a CachedRowSet
object's data is modified while it is
disconnected, it also needs a way to connect to the data source and write the new
data back to it. The reader and writer facilities, based on the RowSetReader
and
RowSetWriter
interfaces and encapsulated in a SyncProvider
implementation,
provide these capabilities.
A writer implementation supplies a mechanism for updating the data source
with any changes it has made while it was disconnected, thus making the modified
data persistent. This can get complicated if there is a conflict, that is, if another
user has already modified the same data in the data source. The SyncProvider
class includes mechanisms that afford varying degrees of control over how accessible
data in the database is to others, and consequently, it has some control over
the number of conflicts that may occur. Even more important, a SyncProvider
implementation determines the level of care to be taken in synchronization.
The JDBC RowSet
Implementations specification includes two different reference
implementations for synchronization using two different levels of concurrency.
The RIXmlProvider
does not check for conflicts and simply writes its
modified data to the data source. The writer defined in the RIOptimisticProvider
implementation checks for conflicts, and if a conflict exists, allows an application
to use a SyncResolver
object to decide whether or not to write modified data on a
case by case basis. Third parties can write SyncProvider
implementations offering
different degrees of synchronization, and a disconnected rowset may use the one
that best fits its needs. The SyncProvider
class and the SyncResolver
interface are
covered in the section "Using the javax.sql.rowset.spi
Package".
The implementation of the CachedRowSet
interface can be used as a basis for
implementing other disconnected rowsets. For example, the implementations of
the WebRowSet
, FilteredRowSet
, and JoinRowSet
interfaces are all based on the
implementation of the CachedRowSet
interface. Therefore, the following discussion
of the CachedRowSet
interface goes into some detail about the methods it
defines and what they do. The sections on the other disconnected rowset implementations
discuss the capabilities they have beyond those of the CachedRowSet
implementation.
RowSet
object is a JavaBeans component, so developers will often create them
using a visual JavaBeans development tool while they are assembling an application.
It is also possible for an application to create an instance at run time, using a
public constructor provided by a class that implements the RowSet
interface. For
example, the standard implementation of the CachedRowSet
interface defines a public
default constructor, so an instance of CachedRowSet
can be created with the following
line of code.
CachedRowSet crset = new CachedRowSetImpl();The newly created
crset
is a container for a set of data, and it will have the
default properties of a BaseRowSet
object. Because its implementation uses the
BaseRowSet
class, crset
can use BaseRowSet
methods to set new values for any of
these properties or to set values for other properties as appropriate. For example,
in the following code fragment, the first line sets the command string that crset
will use to get its data, and the second line turns escape processing off.
crset.setCommand("SELECT * FROM EMPLOYEES"); crset.setEscapeProcessing(false);
CachedRowSet
object must obtain a SyncProvider
object, which implements
a reader and a writer, in order to get data from a data source and to write its modifications
of data back to the data source. The SyncFactory
creates an instance of a
SyncProvider
object that is registered with it when a RowSet
object requests it.
Because the constructor that created crset
was not given any parameters, the SyncFactory
provided an instance of the default, which is RIOptimisticProvider
(one
of the two implementations of the SyncProvider
class provided with the standard
RowSet
implementations).
If the fully qualified class name of a SyncProvider
implementation is supplied
to the constructor, the SyncFactory
will initialize the RowSet
object with an
instance of that SyncProvider
implementation if it has been registered with the
SyncFactory
. The following line of code creates a CachedRowSet
object whose
synchronization provider is an instance of the given SyncProvider
implementation.
CachedRowSet crset = new CachedRowSetImpl("com.supersoftware.providers.HighAvailabilityProvider");After its creation, a
RowSet
object can be set with another registered SyncProvider
implementation, including third-party implementations or the other standard
provider implementation, RIXmlProvider,
which reads and writes a RowSet
object in XML. The way to set a SyncProvider
object is to use the CachedRowSet
method setSyncProvider
, which resets the current SyncProvider
object to the one
specified. For example, if the current provider for crset
is an RIOptimisticProvider
object, the following line of code changes the provider to a com.supersoftware.providers.HighAvailabilityProvider
object.
crset.setSyncProvider("com.supersoftware.providers.HighAvailabilityProvider");
CachedRowSet
object can contain data that was retrieved from a data
source such as a file or spreadsheet, it will probably get its data primarily from a
ResultSet
object via a JDBC driver. The RowSet
interface provides the method execute
, which takes no parameters, and the CachedRowSet
interface adds a second version
of the method execute
that takes a Connection
object as a parameter. Both
methods populate a CachedRowSet
object with data from the ResultSet
object that
is created when the CachedRowSet
object's command is executed. The CachedRowSet
interface also adds the method populate
, which takes a ResultSet
object as
a parameter.
The version of execute
inherited from the RowSet
interface has to establish a
connection behind the scenes before it can execute the rowset's command string.
The version of execute
defined in the CachedRowSet
interface is passed a Connection
object, so it does not need to establish its own connection. The method populate
does not need to establish its own connection or execute a query because it is
passed the ResultSet
object from which it will get its data. Thus, the method populate
does not require that the properties affecting a connection be set before it is
called, nor does it require that the command string be set. The following code
fragment shows how the method populate
might be used.
ResultSet rs = stmt.executeQuery("SELECT NAME, SALARY FROM EMPLOYEES"); // The code that generates rs . . . CachedRowSet crset = new CachedRowSetImpl(); crset.populate(rs);The new
CachedRowSet
object crset
contains the same data that the ResultSet
object
rs
contains.
The implementation details of the method populate
may vary, of course, but
because it is passed a ResultSet
object, populate
is in some ways quite different
from the method execute
. As stated previously, the populate
method does not
need to establish a connection. Nor does it use a reader because it can get its data
directly from the given result set. By using ResultSetMetaData
methods, it can
get information about the format of the result set so that it is able to use the appropriate
getter methods in the ResultSet
interface to retrieve the ResultSet
object's
data.
The methods execute
and populate
do have some similarities. The main similarity
is that both methods change the contents of the entire rowset. As a consequence,
they both cause the following: notification of listeners, setting the original
values equal to the current rowset values, and updating the rowset's metadata
information.
The following code fragments give examples of using the two versions of the
method execute
. Both versions execute a query, so both require that a command
string be set. When no connection has been passed to the method execute
, the
appropriate connection properties must be set before the command can be called.
The execute
method will call the rowset's reader, which will use the necessary
properties to establish a connection with the data source. Once the connection is
established, the reader can call the method executeQuery
to execute the rowset's
command string and get a result set from which to retrieve data.
CachedRowSet crset1 = new CachedRowSetImpl(); crset1.setCommand("SELECT NAME, SALARY FROM EMPLOYEES"); crset1.setDataSourceName("jdbc/myDataSource"); crset1.setUsername("paz"); crset1.setPassword("p38c3"); crset1.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); crset1.execute();In the next example, the
Connection
object con
is passed to the method execute
. The CachedRowSet
object crset2
will use con
for executing the command
string instead of having to establish a new connection behind the scenes.
CachedRowSet crset2 = new CachedRowSetImpl(); crset2.setCommand("SELECT NAME, SALARY FROM EMPLOYEES"); crset2.execute(con);Both
crset1
and crset2
were connected to the data source while the command
string was executed and while the resulting data was read and inserted into it. After
the method execute
returns, both will close their connections to their data sources.
CachedRowSet
object, like all rowsets, uses the getter methods inherited from the
ResultSet
interface to access its data. Because a CachedRowSet
object is implemented
so that it is scrollable by default, it can also use the ResultSet
methods for
moving the cursor. For example, the following code fragment moves the cursor to
the last row of the CachedRowSet
object crset
and then retrieves the String
value in
the first column of that row.
crset.last(); String note = crset.getString(1);A
CachedRowSet
object is implemented such that it always has type ResultSet.TYPE_SCROLL_INSENSITIVE
, so in addition to being scrollable, a CachedRowSet
object is always insensitive to changes made by others. This makes sense because a
CachedRowSet
object is mostly disconnected, and while it is disconnected, it has no
way of seeing the changes that others might make to the underlying data source
from which it got its data.
CachedRowSet
object can have its entire contents
changed with its execute
and populate
methods. It can have one row at a time modified
with the ResultSet
updater methods and the methods insertRow
and deleteRow
. Note that the JDBC RowSet
Implementations specification does not say
where an inserted row should go. In the reference implementations, inserted rows
are put immediately after the current row, but there is great flexibility for deciding
where rows are inserted.
An updater method modifies the value in the specified column in the current row, assigning it the value passed to it (usually as the second parameter). An updater method changes only the value in the rowset, which is cached in memory; it does not affect the value in the underlying data source. Also, it does not affect the value that the rowset keeps track of as the original value.
When an application has made all its updates to a row, it calls the method
updateRow
. In a CachedRowSet
implementation, this method signals that the
updates for the current row are complete, but, like the updater methods, it does not
affect the values in the underlying data source. The updateRow
method likewise
does not affect the values stored as original values. After calling the updateRow
method on all the rows being updated, an application needs to call the CachedRowSet
method acceptChanges
. This method invokes a writer component internally
to propagate changes to the data source backing the rowset, and for each
column value that was changed, it sets the original value to the current value.
Once it has called updateRow
, an application may no longer call the method
cancelRowUpdates
to undo updates to the current row. If it has not yet invoked the
method acceptChanges
, however, it can call the method restoreOriginal
, which
undoes the updates to all rows by replacing the updated values in the rowset with
the original values. The restoreOriginal
method does not need to interact with
the underlying data source.
The following code fragment updates the first two rows in the CachedRowSet
object crset
.
crset.execute(); // crset is initialized with its original and current values crset.first(); crset.updateString(1, "Jane_Austen"); crset.updateFloat(2, 150000f); crset.updateRow(); // the current value of the first row has been updated crset.relative(1); crset.updateString(1, "Toni_Morrison"); crset.updateFloat(2, 120000f); crset.updateRow(); // the current value of the second row has been updated crset.acceptChanges(); // the original value has been set to the current value and the // database has been updated
RowSet
implementations can take advantage of the ability to customize
the retrieval and updating of data that the rowset framework provides. This
applies to rowsets that are disconnected, such as CachedRowSet
objects, because
they require the services of a reader and a writer, which are encapsulated by a SyncProvider
object.
The CachedRowSet
interface defines the method setSyncProvider
, which provides
a CachedRowSet
object with its implementation of a reader and writer. It also
defines a getSyncProvider
method for retrieving a rowset's SyncProvider
object.
A rowset's reader and writer operate completely behind the scenes, performing any
number of tasks that can be customized to provide additional functionality.
The RowSetReader
interface has one public method, readData
, which can be
customized in various ways. For example, a reader can be implemented so that it
reads data straight from a file or from some other non-SQL data source rather than
from a database using a JDBC driver. Such a reader might use the method
RowSet.insertRow
to insert new rows into the rowset. When invoked by a reader,
this method could also be implemented so that it updates the original values stored
by the rowset.
The RowSetWriter
interface has one public method, writeData
, which writes
data that has been modified back to the underlying data source. This method can
likewise be customized in a variety of ways. The writer establishes a connection
with the data source, just as the reader does, and depending on how it is implemented,
may or may not check for conflicts. The RowSetInternal
methods getOriginal
and getOriginalRow
supply the values that existed before the current
modifications, so the writer can compare them with the values read from the data
source to see if the data source has been modified. How the writer decides whether
or not to write data when there is a conflict again depends on how it is implemented.
A CachedRowSet
object that has been configured with a custom reader and/or
writer can be made available as a normal JavaBeans component. This means that
developers writing applications do not have to worry about customizing readers
and writers and can concentrate on the more important aspects of using rowsets
effectively.
CachedRowSet
interface defines a number of other methods. For example, it
defines two versions of the method toCollection
. Sometimes it is more convenient
to work with a rowset's data as elements in a collection, which these methods make
possible by converting a rowset's data into a Java collection. Other CachedRowSet
methods create a copy of the rowset. CachedRowSet.clone
and CachedRowSet.createCopy
create an exact copy of the rowset that is independent from the original. By
contrast, the CachedRowSet.createShared
method creates a rowset that shares its
state with the original rowset. In other words, both the new and the original rowset
share the same physical, in-memory copy of their original and current values. If an
updater method is called on one shared rowset, the update affects the other rowset as
well. In effect, the createShared
method creates multiple cursors over a single set
of rows.
javax.sql.rowset.spi
provides the API that a developer needs to use
to implement a synchronization provider. It includes the following classes and interfaces:
SyncFactory
SyncFactoryException
SyncProvider
SyncProviderException
SyncResolver
XmlReader
XmlWriter
TransactionalWriter
javax.sql
:
A
CachedRowSet
object or other disconnected RowSet
object gets its SyncProvider
object from a SyncFactory
object. Being a static class, there is only one
instance of SyncFactory
, which means that there is only one source from which a
disconnected RowSet
object can obtain its SyncProvider
object. A vendor can register
its implementation of the SyncProvider
abstract class with the SyncFactory
,
which then makes it available for a RowSet
object to plug in. The following line of
code shows one way to register a SyncProvider
implementation with the SyncFactory
.
SyncFactory.registerProvider("com.supersoftware.providers.HAProvider");Note that the argument supplied to
registerProvider
is the fully qualified class
name of the SyncProvider
implementation.
A SyncProvider
implementation may also be registered by adding it to the
system properties. This can be done at the command line at execution time as follows:
-Drowset.provider.classname=com.supersoftware.providers.HAProviderAnother way to register a
SyncProvider
implementation is to add the fully
qualified class name, the vendor, and the version number to the standard properties
file. The reference implementation comes with a properties file that already
has entries for the two reference implementation synchronization providers, RIOptimisticProvider
and RIXmlProvider
. The fourth way to register a SyncProvider
implementation is to register it on a JNDI context and then register the JNDI context
with the SyncFactory
by supplying the fully qualified provider name to the
method SyncFactory.registerJNDIContext
.
When a RowSet
object requests a particular SyncProvider
implementation, the
SyncFactory
will search for it and create an instance of it and return it to the
RowSet
object. For example, in the following line of code, the CachedRowSet
object crset
requests the com.supersoftware.providers.HAProvide
r.
crset.setSyncProvider("com.supersoftware.providers.HAProvider");If the requested provider has not been registered in any of the possible ways, the
SyncFactory
throws a SyncFactoryException
object.
A CachedRowSet
object or any of its subclasses can discover which SyncProvider
implementations have been registered with the SyncFactory
and are thus
available for its use by calling the following line of code.
java.util.Enumeration providers = SyncFactory.getRegisteredProviders();The core components of a
SyncProvider
object are the reader and writer that
it implements. The implementation of the reader determines from what kind of
data source (relational database, flat file, spreadsheet, and so on) it can read data
and also how it reads that data. The implementation of the writer determines what
level of concurrency it uses, whether it checks for conflicts, and how it handles
any conflicts it encounters. The reader in the RIOptimisticProvider,
an implementation
of the RowSetReader
interface, reads data from a relational database.
The writer, an implementation of the RowSetWriter interface, checks for conflicts
and does not write data to the data source if there is a conflict. The reader and
writer implemented in the RIXmlProvider
read and write a rowset as an XML document.
For this reason, it is primarily used by a WebRowSet
object. The writer does
not check for conflicts and simply writes all of a RowSet
object's modifications to
the data source.
The TransactionalWriter
interface allows a CachedRowSet
object that is participating
in a global or local transaction to get fine-grained control of the transactional
boundaries. To get this functionality, a CachedRowSet
object needs to use a
SyncProvider
object that implements the TransactionalWriter
interface.
The reference implementation provides a means by which an application can
choose to resolve conflicts on a case by case basis. After the writer finds all the
conflicts, it throws a SyncProviderException
object. An application can catch that
exception and use it to create a SyncResolver
object, a specialized kind of RowSet
object. A SyncResolver
object mirrors the RowSet
object experiencing the conflicts,
having the same number of rows and columns; however, it contains only the
data that is in conflict.
The SyncResolver
object retrieves each conflict value in a row, comparing the
value in the data source with the value in the RowSet
object. After a decision is
made as to which value should persist for each conflict in the row, the SyncResolver
object sets those values with the method setResolvedValue
. The SyncResolver
object then goes to the next conflict and repeats the process until there are
no more conflicts.
CachedRowSet
interface, being disconnected
and able to operate without a driver, is designed to work especially well with a thin
client for passing data in a distributed application or for making a result set scrollable
and updatable. Many other RowSet
implementations can be designed for other
purposes.
WebRowSet
interface extends the CachedRowSet
interface and therefore has all of
the same capabilities. What it adds is the ability to read and write a rowset in XML
format. A WebRowSet
object uses a WebRowSetXmlReader
object to read a rowset in
XML format and a WebRowSetXmlWriter
object to write a rowset in XML format.
The XML version of a WebRowSet
object contains its metadata, including its properties,
in addition to its data.
The default constructor for a WebRowSet
requests an RIXmlProvider
implementation
so that the WebRowSet
object will be able to read and write a rowset as
an XML document. It is also possible to set a WebRowSet
object with a third-party
SyncProvider
implementation that provides XML capabilities.
A WebRowSet
object is designed to work well in a distributed client/server
application. It is similar to a CachedRowSet
implementation in that both connect a
thin client to an application server. Thus, they are both good for providing data to
a thin client. The difference is that they use different protocols. Because a CachedRowSet
object is serializable, it can be sent to another component using RMI/IIOP
(Remote Method Invocation/Internet Interoperability Protocol). A WebRowSet
object uses HTTP/XML (Hypertext Transfer Protocol/eXtensible Markup Language)
to communicate with the middle tier, so that, for example, Web clients can
talk to Java servlets that provide data access.
XML has become more and more important for Web services because of the
portability of data it provides. The JDBC RowSet
Implementations specification
includes a standard WebRowSet
XML Schema, available at http://java.sun.com/xml/ns/jdbc/webrowset.xsd
, to which a standard WebRowSet
object adheres. This
means that if two parties have the XML schema for a WebRowSet
object, they can
use it to exchange rowsets in a common format even though they may store their
data internally in entirely different formats. This makes a WebRowSet
object a powerful
tool for data exchange.
Because the Java platform provides portability of code and XML provides portability of data, they are the ideal combination for Web services. Technologies like JavaTM API for XML-based RPC, SOAP with Attachments API for JavaTM, JavaTM Architecture for XML Binding, and JavaTM API for XML Registries make developing Web services easier and easier. Plus the infrastructure that the J2EE platform provides saves developers from having to program their own "plumbing" for the management of distributed transactions, connection pooling, and security.
Being able to use a WebRowSet
object for sending data adds even more to the
value of using the Java platform for Web services. For example, because it uses
the standard XML schema, a WebRowSet
object can be part of a Web services message
(generally using SOAP with Attachments API for Java technology).
The following code fragment is a simple example that creates the WebRowSet
object wrs
, populates it with the data of the ResultSet
object rs
, and then updates
a column value. The final two lines output wrs
in XML format.
WebRowSet wrs = new WebRowSetImpl(); wrs.populate(rs); //perform updates wrs.absolute(2) wrs.updateString(1, "newString"); FileWriter fWriter = new FileWriter("/share/net/output.xml"); wrs.writeXml(fWriter);The machine that receives the XML output will do something similar to the following to read it. Once
wrs
has read in the data and metadata in fReader
, the
code operates on it just as it would on any other WebRowSet
object.
WebRowSet wrs = new WebRowsetImpl(); FileReader fReader = new FileReader("/share/net/output.xml"); wrs.readXml(fReader); wrs.absolute(2); String str = wrs.getString(1);
FilteredRowSet
object, an extension of a CachedRowSet
object, lets a programmer
use a filtered subset of data from a rowset. For example, suppose a FilteredRowSet
object contains a fairly large set of rows. A programmer who wants to
perform operations on only a subset of those rows can specify a range of values, and
the FilteredRowSet
object will return only values in that range. This filtering is
done by the method next
, which is implemented to skip any rows that do not fall
within the specified range. Without this capability, the programmer would have to
make a connection to the data source, get a ResultSet
object with the selected rows,
and populate a rowset with those rows. Because establishing a connection is very
expensive, far outweighing the cost of the memory needed to store a FilteredRowSet
object, the ability to get a selected set of rows without making a new connection
often results in a significant improvement in performance.
Suppose you have a FilteredRowSet
object frs
that contains all of the
employees in Company XYZ. You have figured the average salary for the company,
including everyone in the average. Now you want to get information about
only the employees whose names range from Aaronson to Lee. The following
code fragment causes frs
to make available only the rows where the values in the
column NAMES
are in the range of Aaronson to Lee.
In the following code fragment, the FilteredRowSet
object frs
is populated
with data from the ResultSet
object rs
. Then the code creates a Range
object,
specifying that the last names Aaronson through Lee, which are in the column
NAME
, make up the range of names that the method next
can return.
FilteredRowSet frs = new FilteredRowSetImpl(); frs.populate(rs); Range names = new Range("Aaronson", "Lee", findColumn("NAME")); frs.setFilter(names); while (frs.next()) { String name = frs.getString("NAME"); . . . // add each name to, for example, a mailing list // only names from "Aaronson" to "Lee" will be returned }If some of the data in the subset stored in a
FilteredRowSet
object needs to be
changed, it can be modified and synchronized with the data source. Only the modifications
in the subset will be synchronized. If, however, the source of the data is
an SQL VIEW
that is not updatable, indicated by the constant SyncProvider.NONUPDATABLE_VIEW_SYNC
, the modifications to the FilteredRowSet
object
will not be synchronized.
JoinRowSet
object lets a programmer combine data from two different RowSet
objects. This can be especially valuable when related data is stored in different data
sources. Any RowSet
implementation can participate in a join, but it is typically two
CachedRowSet
objects that are joined. With all of the relevant data combined into
one JoinRowSet
object, an application can process the data just as it would for any
other kind of RowSet
object.
The following code fragment demonstrates how the data from two CachedRowSet
objects is joined into one JoinRowSet
object. In this scenario, data from
the table EMPLOYEES
is joined with data from the table BONUS_PLAN
, thereby giving
information about each employee and his or her bonus plan all in one rowset. In
both of the original rowsets, the first column is the employee identification number,
so that is the column used to match the data from one table with data from the
other. After the join, each row in the JoinRowSet
object will contain the columns
from both rowsets that pertain to the same employee ID.
The first line of code creates the JoinRowSet
object jrs
. Next, all the columns
from the table EMPLOYEES
are used to populate the new CachedRowSet
object empl
,
the first column of empl
is set as the match column, and empl
is added to jrs
.
Then, in similar fashion, all the columns from the table BONUS_PLAN
are used to
populate the CachedRowSet
object bonus
, the first column of bonus
is set as the
match column, and bonus
is added to jrs
. In both empl
and bonus
, the first column
is EMPLOYEE_ID
, which is the primary key. It is the column both rowsets have in
common, and it can be used to match the information from empl
about an
employee with the information in bonus
about the same employee. The last lines
of code navigate jrs
and retrieve a column value.
JoinRowSet jrs = new JoinRowSetImpl(); ResultSet rs1 = stmt.executeQuery("SELECT * FROM EMPLOYEES"); CachedRowSet empl = new CachedRowSetImpl(); empl.populate(rs1); empl.setMatchColumn(1); // The first column is EMPLOYEE_ID jrs.addRowSet(empl); ResultSet rs2 = stmt.executeQuery("SELECT * FROM BONUS_PLAN"); CachedRowSet bonus = new CachedRowSetImpl(); bonus.populate(rs2); bonus.setMatchColumn(1); // The first column is EMPLOYEE_ID jrs.addRowSet(bonus); // The jrs instance now joins the two rowsets. The application // can browse the combined data as if it were browsing one single // RowSet object. jrs.first(); int employeeID = jrs.getInt(1); String employeeName = jrs.getString(2);Similar to the case with
FilteredRowSet
objects, a JoinRowSet
object can
have its modified data synchronized providing that the source from which the data
came is not a non-updatable SQL VIEW
.