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

### 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:

- `JdbcRowSet`
- `CachedRowSet`
- `WebRowSet`
- `FilteredRowSet`
- `JoinRowSet`

### 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:

- Properties--fields for storing property values and methods for getting and setting property values
- Event notification mechanism--methods for adding and deleting listeners and for notifying listeners of changes
- Setter methods--a complete set of methods for setting the placeholder parameters in a `RowSet` object's command property
- Default values--all `RowSet` implementations have the following default values:
  - Is scrollable and does not show changes made by others
  - Is updatable
  - Does not show rows that have been deleted
  - Has no time limit for how long a driver may take to execute the `RowSet` object's command
  - Has no limit for the number of bytes that columns of type `BINARY`, `VARBINARY`, `LONGVARBINARY`, `CHAR`, `VARCHAR`, or`LONGVARCHAR` may contain
  - Does not see uncommitted data (make "dirty" reads)
  - Has escape processing is turned on
  - Has its connection's type map is set to `null`
  - Has an empty `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:

- Connected (maintains a connection to the data source using a JDBC driver)
  - `JdbcRowSet`
- Disconnected (not connected to the data source except when reading data from it or writing data to it)
  - `CachedRowSet`
  - `JoinRowSet`
  - `FilteredRowSet`
  - `WebRowSet`

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:

- `SyncFactory`
- `SyncFactoryException`
- `SyncProvider`
- `SyncProviderException`
- `SyncResolver`
- `XmlReader`
- `XmlWriter`
- `TransactionalWriter`

The following two interfaces are inherited from the package `javax.sql`:

- `RowSetReader`
- `RowSetWriter`

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

### 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]

Copyright © 1996-2004 Sun Microsystems, Inc. All rights reserved.