[Top] [Prev] [Next]

RowSet

10.1    RowSet Overview

A 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.

10.1.1     The Event Model for Rowsets

The 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);

10.1.2     Properties for a Rowset

The 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.getConcurrencysetConcurrency
getDataSourceNamesetDataSourceName
getEscapeProcessingsetEscapeProcessing
getMaxFieldSizesetMaxFieldSize
getMaxRowssetMaxRows
getPasswordsetPassword
getQueryTimeoutsetQueryTimeout
getTransactionIsolationsetTransactionIsolation
ResultSet.getTypesetType
getTypeMapsetTypeMap
getUrlsetUrl
getUsernamesetUsername

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).

10.1.3     Setting Parameters for the Command String

The example in the previous section uses a command that has no placeholders for parameters, but the command property may also be set with a query that takes input parameters. To make this possible, the internal representation of the command string is a 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.

10.1.4     Traversing a RowSet Object

The 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. .

10.1.5     Executing a Command

The 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.

10.1.6     Using a RowSet Object's Metadata

A 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);

10.2    Standard Implementations

The Java platform includes five standard implementations of the 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:

10.2.1     Implementation Basics

All of the standard 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:

As stated previously, RowSet objects may be connected or disconnected. The RowSet implementations fall into the following categories:

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.

10.2.2     Overview of the JdbcRowSet Implementation

A 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);

10.2.3     Overview of the CachedRowSet Implementation

The standard implementation of the 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.

10.2.4     Uses for a CachedRowSet Object

Being disconnected and serializable, a 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.

10.2.5     Creating a CachedRowSet Object

Any 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);

10.2.6     Setting a SyncProvider Object

Every 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");

10.2.7     Populating a CachedRowSet Object

Although a 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.

10.2.8     Accessing Data

A 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.

10.2.9     Modifying Data

As shown in the previous section, a 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

10.2.10     Customizing Readers and Writers

Disconnected 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.

10.2.11     Other Methods

The 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.

10.2.12     Using the javax.sql.rowset.spi Package

The package 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:

The following two interfaces are inherited from the package 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.HAProvider
Another 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.HAProvider.

    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.

10.2.13     CachedRowSet Summary

In summary, an implementation of the 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.

10.2.14     WebRowSet Implementation

The 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);

10.2.15     FilteredRowSet Implementation

A 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.

10.2.16     JoinRowSet Implementation

A 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.

    
    

[Top] [ Prev] [Next]

jdbc@eng.sun.com or jdbc-business@eng.sun.com
Copyright © 1996-2004 Sun Microsystems, Inc. All rights reserved.