JDBC TM RowSet Implementations Tutorial
JDBC TM RowSet Implementations Tutorial
5
RowSet Overview 5
What Can RowSet Objects Do?
Function as a JavaBeans Component 6
Properties 6
Event Notification 7
Add Scrollability or Updatability 8
Kinds of RowSet Objects
Connected RowSet Objects 8
Disconnected RowSet Objects 8
11
JdbcRowSet 11
Creating a JdbcRowSet Object
Passing a ResultSet Object 12
Using the Default Constructor 13
Setting Properties 14
Setting Parameters for the Command 15
Using a JdbcRowSet Object
Navigating a JdbcRowSet Object 17
Updating a Column Value 18
Inserting a Row 18
Deleting a Row 19
Code Sample
11
16
20
23
CachedRowSet 23
Setting Up a CachedRowSet Object
Creating a CachedRowSet Object 24
Using the Default Constructor 24
Passing a SyncProvider Implementation 24
Setting Properties 25
Setting Key Columns 26
Populating a CachedRowSet Object
What a Reader Does 27
Updating a CachedRowSet Object
Updating a Column Value 29
Inserting and Deleting Rows 29
Updating the Data Source
What a Writer Does 30
Using the Default Implementation 31
Using a SyncResolver Object 31
Using Other SyncProvider Implementations 33
23
26
28
30
2
Notifying Listeners
Setting Up Listeners 34
How Notification Works 35
Accessing Large Amounts of Data
Code Sample
34
35
37
43
JoinRowSet 43
Creating a JoinRowSet Object
Adding RowSet Objects
Passing the Match Column to addRowSet 46
Using Joinable.setMatchColumn 47
Using Multiple Columns as the MatchColumn 47
Using a JoinRowSet Object
Code Sample
43
44
48
49
53
FilteredRowSet 53
Creating a Predicate Object
Creating a FilteredRowSet Object 58
Creating and Setting a Predicate Object 59
Working with Filters
Updating a FilteredRowSet Object
Inserting or Updating a Row 62
Deleting a Row 63
Combining Two Filters into One 64
Code Samples
Code Sample 1 65
Code Sample 2 71
Code Sample 3 76
54
60
62
65
81
WebRowSet 81
Creating and Populating a WebRowSet Object
Writing and Reading a WebRowSet Object to XML 83
Using the writeXml Method 83
Using the readXml Method 84
What Is in the XML Document
Properties 86
Metadata 87
Data 88
Making Changes to a WebRowSet Object
Inserting a Row 90
Deleting a Row 91
82
84
90
3
Modifying a Row 91
WebRowSet Code Example
WebRowSet XML Schema
92
95
1
RowSet Overview
A
JDBC RowSet object holds tabular data in a way that makes it more exible
and easier to use than a result set. Sun Microsystems has dened ve RowSet
interfaces for some of the more popular uses of a RowSet object, and the Java
Community Process has produced standard reference implementations for these
ve RowSet interfaces. In this tutorial you will learn how easy it is to use these
reference implementations, which together with the interfaces are part of the
Java 2 Platform, Standard Edition 5.0 (J2SE 5.0).
Sun provides the ve versions of the RowSet interface and their implementations
as a convenience for developers. Developers are free write their own versions of
the javax.sql.RowSet interface, to extend the implementations of the ve RowSet
interfaces, or to write their own implementations. However, many programmers
will probably nd that the standard reference implementations already t their
needs and will use them as is.
This chapter gives you an overview of the ve RowSet interfaces, and the succeeding chapters walk you through how to use each of the reference implementations.
ROWSET OVERVIEW
Properties
All RowSet objects have properties. A property is a eld that has the appropriate
getter and setter methods in the interface implementation. For example, the BaseRowSet abstract class, a convenience class in the JDBC RowSet Implementations,
provides the methods for setting and getting properties, among other things. All
of the RowSet reference implementations extend this class and thereby have
access to these methods. If you wanted to add a new property, you could add the
getter and setter methods for it to your implementation. However, the BaseRowSet
class provides more than enough properties for most needs.
Just because there are getter and setter methods for a property does not mean that
you must set a value for every property. Many properties have default values, and
setting values for others is optional if that property is not used. For example, all
RowSet objects must be able to obtain a connection to a data source, which is generally a database. Therefore, they must have set the properties needed to do that.
You can get a connection in two different ways, using the DriverManager mechanism or using a DataSource object. Both require the username and password properties to be set, but using the DriverManager requires that the url property be set,
whereas using a DataSource object requires that the dataSourceName property be set.
The default value for the type property is ResultSet.TYPE_SCROLL_INSENSITIVE and
for the concurrency property is ResultSet.CONCUR_UPDATABLE. If you are working
with a driver or database that does not offer scrollable and updatable ResultSet
objects, you can use a RowSet object populated with the same data as a ResultSet
object and thereby effectively make that ResultSet object scrollable and updatable.
You will see how this works in the chapter JdbcRowSet.
The following BaseRowSet methods set other properties:
setCommand
setEscapeProcessingdefault
setFetchDirection
setFetchSize
is on
setMaxFieldSize
setMaxRows
setQueryTimeoutdefault is no time limit
setShowDeleteddefault is not to show deleted rows
setTransactionIsolationdefault is not to see dirty reads
setTypeMapdefault is null
You will see a lot more of the command property in future chapters.
Event Notication
RowSet objects use the JavaBeans event model, in which registered components are
notied when certain events occur. For all RowSet objects, three events trigger
notications:
1. A cursor movement
2. The update, insertion, or deletion of a row
3. A change to the entire RowSet contents
The notication of an event goes to all listeners, components that have implemented the RowSetListener interface and have had themselves added to the RowSet
objects list of components to be notied when any of the three events occurs.
A listener could be a GUI component such as bar graph. If the bar graph is tracking data in a RowSet object, it would want to know the new data values whenever
the data changed. It would therefore implement the RowSetListener methods to
dene what it will do when a particular event occurs. Then it also needs to be
added to the RowSet objects list of listeners. The following line of code registers
the bar graph component bg with the RowSet object rs.
rs.addListener(bg);
Now bg will be notied each time the cursor moves, a row is changed, or all of rs
gets new data.
ROWSET OVERVIEW
data over a network. They can even be used for sending data to thin clients such
as PDAs and mobile phones.
The CachedRowSet interface denes the basic capabilities available to all disconnected RowSet objects. The other three are extensions of it providing more specialized capabilities. The following outline shows how they are related.
CachedRowSet
WebRowSet
JoinRowSet
FilteredRowSet
A CachedRowSet object has all the capabilities of a JdbcRowSet object plus it can
also do the following:
Obtain a connection to a data source and execute a query
Read the data from the resulting ResultSet object and populate itself with
that data
Manipulate data and make changes to data while it is disconnected
Reconnect to the data source to write changes back to it
Check for conicts with the data source and resolve those conicts
A WebRowSet object has all the capabilities of a CachedRowSet object plus it can
also do the following:
Write itself as an XML document
Read an XML document that describes a WebRowSet object
A JoinRowSet object has all the capabilities of a WebRowSet object (and therefore
also a CachedRowSet object) plus it can also do the following:
Form the equivalent of an SQL JOIN without having to connect to a data
source
A FilteredRowSet object likewise has all the capabilities of a WebRowSet object (and
therefore also a CachedRowSet object) plus it can also do the following:
Apply ltering criteria so that only selected data is visible. This is equivalent to executing a query on a RowSet object without having to use a query
language or connect to a data source.
ROWSET OVERVIEW
The following chapters walk you through how to use the reference implementations for each of the interfaces introduced in this chapter.
2
JdbcRowSet
A
One of the main uses of a JdbcRowSet object is to make a ResultSet object scrollable
and updatable when it does not otherwise have those capabilities.
In this chapter, you will learn how to:
JDBCROWSET
Note that because no arguments are passed to the method createStatement, any
ResultSet objects it produces will be neither scrollable nor updatable. As a result,
you can move the cursor for rs only forward, and you cannot make changes to the
data in rs. However, we now have the data from rs in jdbcRs, and you can move the
cursor for jdbcRs to any position and can also modify the data in jdbcRs.
Because the newly created JdbcRowSet object jdbcRs contains exactly the same
data as rs, it can be considered a wrapper for rs. Assume that Table 21COFFEES
represents the data in both rs and jdbcRs.
Table 21 COFFEES
COF_ID
COF_NAME
SUP_ID
PRICE
1250
Colombian
101
7.99
1300
French_Roast
49
8.99
1800
Espresso
150
9.99
2250
Colombian_Decaf
101
8.99
Being scrollable and updatable are only two of the default properties of a escape
Processing object. In addition to populating jdbcRs with the data from rs, the constructor also sets the following properties with the following values:
typeResultSet.TYPE_SCROLL_INSENSITIVE
The main thing you need to remember from this list is that a JdbcRowSet and all
other RowSet objects are scrollable and updatable unless you set different values
for those properties.
All of the reference implementation constructors assign the default values for the
porperties listed in the section Passing a ResultSet Object, so although jdbcRs2
has no data yet, it has the same properties set with default values as jdbcRs. To
populate jdbcRs2 with data, you need a ResultSet object with the desired data. This
means you need to get a connection and execute a query, which requires your
setting the properties needed for getting a connection and setting the query to be
executed. You will see how to set these properties in the next section.
10
JDBCROWSET
Setting Properties
The section Passing a ResultSet Object lists the properties that are set by
default when a new RowSet object is created. If you use the default constructor,
you need to set some additional properties before you can populate your new JdbcRowSet object with data.
In order to get its data, a JdbcRowSet object rst needs to connect to a database.
The following four properties hold information used in obtaining a connection to
a database.
usernamethe
As was mentioned in the chapter Overview, which of these properties you need
to set depends on how you are going to make a connection. The preferred way is
to use a DataSource object, but it may not be practical for some readers to register
a DataSource object with a JNDI naming service, which is generally done by a person acting in the capacity of a system administrator. Therefore, the code examples all use the DriverManager mechanism to obtain a connection, for which you
use the url property and not the datasourceName property.
The following lines of code set the username, password, and url properties so that a
connection can be obtained using the DriverManager mechanism. (You will nd the
JDBC URL to set as the value for the url property in the documentation for your
JDBC driver.)
jdbcRs.setUsername("hardy");
jdbcRs.setPassword("oursecret");
jdbcRs.setUrl("jdbc:mySubprotocol:mySubname");
Another property that you must set is the command property. This property is the
query that determines what data the JdbcRowSet object will hold. For example, the
following line of code sets the command property with a query that produces a
ResultSet object containing all the data in the table COFFEES.
jdbcRs.setCommand("select * from COFFEES");
Once you have set the command property and the properties necessary for making
a connection, you are ready to populate jdbcRs with data. You can do this by simply calling the execute method.
jdbcRs.execute();
The execute method does many things for you behind the scenes.
1. It makes a connection to the database using the values you assigned to the
url, username, and password properties.
2. It executes the query you set for the command property.
3. It reads the data from the resulting ResultSet object into jdbcRs.
At this point, jdbcRs and jdbcRs2 should be identical.
For more exibility, you could use a placeholder parameter instead of 7.99. A
placeholder parameter is a question mark (?) used in place of a literal value.
select COF_NAME, PRICE from COFFEES where PRICE > ?;
In this case, you have to supply the value for the placeholder parameter before
you can execute the query. A query with placeholder parameters is a PreparedStatement object, and you use the equivalent of PreparedStatement setter methods to supply a placeholder parameter value, as is done in the following line of code. The
rst argument is the ordinal position of the placeholder parameter, and the second argument is the value to assign to it. When there is only one placeholder
parameter, its ordinal position is, of course, one.
jdbcRs.setBigDecimal(1, new BigDecimal("8.99"));
11
12
JDBCROWSET
If your query has two placeholder parameters, you must set values for both of
them.
select COF_NAME, PRICE from COFFEES where PRICE > ? and SUP_ID = ?;
jdbcRs.setBigDecimal(1, new BigDecimal("8.99"));
jdbcRs.setInt(2, 101);
Note that ordinal position is the placeholder parameters position in the command and has nothing to do with its column index in the ResultSet object or in
jdbcRs.
following code fragment goes to each row in COFFEES and prints out the values
in the columns COF_NAME and PRICE. The method next initially puts the cursor
above the rst row so that when it is rst called, the cursor moves to the rst row.
On subsequent calls, this method moves the cursor to the next row. Because next
returns true when there is another row and false when there are no more rows, it
can be put into a while loop. This moves the cursor through all of the rows,
repeatedly calling the method next until there are no more rows. As noted earlier,
this is the only cursor movement method that a nonscrollable ResultSet object can
call.
while (jdbcRs.next()) {
String name = jdbcRs.getString("COF_NAME");
BigDecimal price = jdbcRs.getBigDecimal("PRICE");
System.out.println(name + "
" + price);
}
A JdbcRowSet object can call the method next , as seen in the preceding code fragment, and it can also call any of the other ResultSet cursor movement methods.
For example, the following lines of code move the cursor to the fourth row in
jdbcRs and then to the third row.
jdbcRs.absolute(4);
jdbcRs.previous();
The method previous is analogous to the method next in that it can be used in a
while loop to traverse all of the rows in order. The difference is that you must
move the cursor to after the last row, and previous moves the cursor toward the
beginning.
jdbcRs.afterLast();
while (jdbcRs.previous()) {
String name = jdbcRs.getString("COF_NAME");
BigDecimal price = jdbcRs.getBigDecimal("PRICE");
System.out.println(name + "
" + price);
}
The output for this piece of code will have the same data as the code fragment
using the method next, except the rows will be printed in the opposite order,
going from the last row to the rst.
You will see the use of more cursor movement methods in the section on updating data.
13
14
JDBCROWSET
The code moves the cursor to the third row, changes the value for the column
"PRICE" to 10.99, and then updates the database with the new price. There are
two things to note. First, for the rst argument to the method updatetBigDecimal, we
could have given the column number (which in this case is 4) instead of the column name.
Second, the data type for this column is an SQL DECIMAL, which is commonly
used for columns with money values. The DECIMAL type takes two parameters,
so the full data type for the column PRICE is DECIMAL(6, 2). The rst parameter
indicates the precision, or total number of digits. The second parameter indicates
the number of digits to the right of the decimal point. So values in the PRICE column can have six digits, four digits before the decimal point and two digits after
the decimal point. The recommended ResultSet getter method for retrieving values
of type DECIMAL is getBigDecimal. Because BigDecimal is an Object type, you have
to pass a BigDecimal object to the methods setBigDecimal and updateBigDecimal. This
explains why the value being set is new BigDecimal("10.99"), which creates a BigDecimal object with the value 10.99. You can use a number as the parameter to new
BigDecimal, but we use a String object because it is safer.
Databases vary in the names they use for data types, so if your database does not
use DECIMAL, you can call the DatabaseMetaData method getTypeInfo to see what
your database uses. The method getTypeInfo returns a ResultSet object with one row
for each data type. The rst column, TYPE_NAME, gives the name the database
uses for a type. The second column, DATA_TYPE, gives the type code for the corresponding JDBC type (from the class java.sql.Types). The type code for DECIMAL
is 3, so you want to use the name in the TYPE_NAME column of the row where the
DATA_TYPE column value is 3. This is the type name to use in the CREATE TABLE
statement for the data type of the column PRICE.
The third thing to note is that calling the method updateRow updates the database,
which is true because jdbcRs has maintained its connection to the database. For
INSERTING A ROW
disconnected RowSet objects, the situation is different, as you will see in the
chapter CachedRowSet.
Inserting a Row
If the owner of the Coffee Break chain wants to add one or more coffees to what
he offers, he will need to add one row to the COFFEES table for each new coffee,
as is done in the following code fragment. You will notice that because jdbcRs is
always connected to the database, inserting a row into a JdbcRowSet object is the
same as inserting a row into a ResultSet object: You move to the insert row, use the
appropriate updater method to set a value for each column, and call the method
insertRow.
jdbcRs.moveToInsertRow();
jdbcRs.updateString("COF_NAME", "House_Blend");
jdbcRs.updateInt("SUP_ID", 49);
jdbcRs.updateBigDecimal("PRICE", new BigDecimal("7.99"));
jdbcRs.updateInt("SALES", 0);
jdbcRs.updateInt("TOTAL", 0);
jdbcRs.insertRow();
jdbcRs.moveToCurrentRow();
jdbcRs.moveToInsertRow();
jdbcRs.updateString("COF_NAME", "House_Blend_Decaf");
jdbcRs.updateInt("SUP_ID", 49);
jdbcRs.updateBigDecimal("PRICE", new BigDecimal("8.99"));
jdbcRs.updateInt("SALES", 0);
jdbcRs.updateInt("TOTAL", 0);
jdbcRs.insertRow();
jdbcRs.moveToCurrentRow();
When you call the method insertRow, the new row is inserted into jdbcRs and is
also inserted into the database. The preceding code fragment goes through this
process twice, so two new rows are inserted into jdbcRs and the database.
Deleting a Row
As is true with updating data and inserting a new row, deleting a row is just the
same for a JdbcRowSet object as for a ResultSet object. The owner wants to discontinue selling French Roast decaf coffee, which is the last row in jdbcRs. In the fol-
15
16
JDBCROWSET
lowing lines of code, the rst line moves the cursor to the last row, and the
second line deletes the last row from jdbcRs and from the database.
jdbcRs.last();
jdbcRs.deleteRow();
Code Sample
The following code sample, which you will nd in the samples directory, is a
complete, runnable program incorporating code fragments shown in this chapter.
The code does the following:
1.
2.
3.
4.
5.
Declares variables
Loads the driver and gets a connection
Creates the table COFFEES
Creates a Statement object and executes a query
Creates a new JdbcRowSet object initialized with the ResultSet object that was
produced by the execution of the query
6. Moves to the third row and updates the PRICE column in that row
7. Inserts two new rows, one for HOUSE_BLEND and one for
HOUSE_BLEND_DECAF
CODE SAMPLE
String strUrl = "jdbc:datadirect:oracle://" +
"129.158.229.21:1521;SID=ORCL9";
String strUserId = "scott";
tring strPassword = "tiger";
String className = "com.ddtek.jdbc.oracle.OracleDriver";
JdbcRowSet jdbcRs;
ResultSet rs;
Statement stmt;
Connection con;
BigDecimal b;
try {
Class.forName(className);
} catch(java.lang.ClassNotFoundException e) {
System.err.print("ClassNotFoundException: ");
System.err.println(e.getMessage());
}
try {
con = DriverManager.getConnection(
strUrl, strUserId, strPassword);
con.setAutoCommit(false);
stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
stmt.addBatch("drop table COFFEES");
stmt.addBatch("create table COFFEES(COF_ID INTEGER, " +
"COF_NAME VARCHAR(20), SUP_ID INTEGER, " +
"PRICE DECIMAL(6,2))");
b = new BigDecimal("7.99");
stmt.addBatch("insert into COFFEES values " +
"(1250, Colombian, 101, "+b+")");
b = new BigDecimal("8.99");
stmt.addBatch("insert into COFFEES values " +
"(1300, French_Roast, 49, "+b+")");
b = new BigDecimal("9.99");
stmt.addBatch("insert into COFFEES values " +
"(1800, Espresso, 150, "+b+")");
b = new BigDecimal("8.99");
stmt.addBatch("insert into COFFEES values " +
17
18
JDBCROWSET
"(2250, Colombian_Decaf, 101, "+b+")");
stmt.executeBatch();
rs = stmt.executeQuery("select * from COFFEES");
jdbcRs = new JdbcRowSetImpl(rs);
jdbcRs.absolute(3);
jdbcRs.updateBigDecimal("PRICE", new BigDecimal("9.99"));
jdbcRs.updateRow();
jdbcRs.first();
jdbcRs.moveToInsertRow();
jdbcRs.updateString("COF_NAME", "House_Blend");
jdbcRs.updateInt("SUP_ID", 49);
jdbcRs.updateBigDecimal("PRICE", new BigDecimal("7.99"));
jdbcRs.insertRow();
jdbcRs.moveToCurrentRow();
jdbcRs.moveToInsertRow();
jdbcRs.updateString("COF_NAME", "House_Blend_Decaf");
jdbcRs.updateInt("SUP_ID", 49);
jdbcRs.updateBigDecimal("PRICE", new BigDecimal("8.99"));
jdbcRs.insertRow();
jdbcRs.moveToCurrentRow();
jdbcRs.last();
jdbcRs.deleteRow();
con.close();
jdbcRs.close();
} catch(SQLException sqle) {
System.out.println("SQL Exception encountered: " + sqle.getMessage());
}
}
}
3
CachedRowSet
A
19
20
CACHEDROWSET
SETTING PROPERTIES
Setting Properties
Generally, the default values for properties are ne as they are, but you may
change the value of a property by calling the appropriate setter method. And
there are some properties without default values that you need to set yourself.
In order to get data, a disconnected RowSet object needs to be able to connect to a
data source and have some means of selecting the data it is to hold. Four properties hold information necessary to obtain a connection to a database.
usernamethe
As was mentioned in the chapter Overview, which of these properties you need
to set depends on how you are going to make a connection. The preferred way is
to use a DataSource object, but it may not be practical for some readers to register
a DataSource object with a JNDI naming service, which is generally done by a person acting in the capacity of a system administrator. Therefore, the code examples all use the DriverManager mechanism to obtain a connection, for which you
use the url property and not the datasourceName property.
The following lines of code set the username, password, and url properties so that a
connection can be obtained using the DriverManager mechanism. (You will nd the
JDBC URL to set as the value for the url property in the documentation for your
JDBC driver.)
crs.setUsername("hardy");
crs.setPassword("oursecret");
crs.setUrl("jdbc:mySubprotocol:mySubname");
Another property that you must set is the command property. In the reference
implementation, data is read into a RowSet object from a ResultSet object. The
query that produces that ResultSet object is the value for the command property. For
example, the following line of code sets the command property with a query that
produces a ResultSet object containing all the data in the table COF_INVENTORY.
crs.setCommand("select * from COF_INVENTORY");
You will see how the command property is used and crs is lled with data later in
this chapter.
21
22
CACHEDROWSET
The rst column in the table COFFEES is COF_NAME. It can serve as the key column because every coffee name is different and therefore uniquely identies one
row and only one row in the table COFFEES. The method setKeyColumns takes an
array to allow for the fact that it may take two or more columns to identify a row
uniquely.
Key columns are used internally, so after setting them, you do nothing more with
them. However, it is important that you set key columns because a SyncResolver
object cannot identify specic rows without that information. You will see how
to use a SyncResolver object in the section Using a SyncResolver Object, on
page 27.
The data in crs is the data in the ResultSet object produced by executing the query
in the command property.
What is different is that the CachedRowSet implementation for the method execute
does a lot more than the JdbcRowSet implementation. Or more correctly, the
23
a lot more.
Every disconnected RowSet object has a SyncProvider object assigned to it, and this
SyncProvider object is what provides the RowSet objects reader (a RowSetReader
object). When we created crs, we used the default CachedRowSetImpl constructor,
which, in addition to setting default values for properties, assigns an instance of
the RIOptimisticProvider implementation as the default SyncProvider object.
RowSet
Table 31 COF_INVENTORY
WAREHOUSE_ID
COF_NAME
SUP_ID
QUAN
DATE
1234
House_Blend
49
2006_04_01
1234
House_Blend_Decaf
49
2006_04_01
1234
Colombian
101
2006_04_01
1234
French_Roast
49
2006_04_01
1234
Espresso
150
2006_04_01
24
CACHEDROWSET
WAREHOUSE_ID
COF_NAME
SUP_ID
QUAN
DATE
1234
Colombian_Decaf
101
2006_04_01
been set with a new value, the method updateRow is called to save the new value to
memory.
int [] quantity = {873, 927, 985, 482, 358, 531};
int len = quantity.length;
crs.beforeFirst();
while (crs.next()) {
for(int i = 0; i < len; i++) {
crs.updateInt("QUAN", quantity[i]);
crs.updateRow();
}
}
25
26
CACHEDROWSET
the row where the value in the COF_NAME column is Espresso and deletes it from
crs.
while (crs.next()) {
if (crs.getString("COF_NAME").equals("Espresso")) {
crs.deleteRow();
break;
}
}
even check for conicts and just writes all changes to the database. This is the
case with the RIXMLProvider implementation, which is used by a WebRowSet
object. At the other end, the writer makes sure there are no conicts by setting
database locks that prevent others from making changes.
The writer for crs is the one provided by the default SyncProvider implementation,
RIOptimisticProvider. The RIOPtimisticProvider implementation gets its name from the
fact that it uses an optimistic concurrency model. This model assumes that there
will be few, if any, conicts and therefore sets no database locks. The writer
checks to see if there are any conicts, and if there are none, it writes the
changes made to crs to the database to be persisted. If there are any conicts, the
default is not to write the new RowSet values to the database.
In our scenario, the default behavior works very well. Because no one at headquarters is likely to change the value in the QUAN column of COF_INVENTORY,
there will be no conicts. As a result, the values entered into crs at the warehouse
will be written to the database and thus persisted, which is the desired outcome.
is a RowSet object that replicates crs except that it contains only the values
in the database that caused a conict. All other column values are null.
resolver
27
28
CACHEDROWSET
With resolver in hand, you can iterate through its rows to locate the values that are
not null and are therefore values that caused a conict. Then you can locate the
value at the same position in crs and compare them. The following code fragment
retrieves resolver and uses the SyncResolver method nextConflict to iterate through the
rows that have conict values. resolver gets the status of each conict value, and if
it is UPDATE_ROW_CONFLICT, meaning that the crs was attempting an update
when the conict occurred, resolver gets the row number of that value. Then the
code moves the cursor for crs to the same row. Next, the code nds the column in
that row of resolver that contains a conict value, which will be a value that is not
null. After retrieving the value in that column from both resolver and crs, you can
compare the two and decide which one you want to be persisted. Finally, the
code sets that value in both crs and the database using the method setResolvedValue.
try {
crs.acceptChanges();
} catch (SyncProviderException spe) {
SyncResolver resolver = spe.getSyncResolver();
Object crsValue; // value in crs
Object resolverValue; // value in the SyncResolver object
Object resolvedValue; // value to be persisted
while (resolver.nextConflict()) {
if (resolver.getStatus() == SyncResolver.UPDATE_ROW_CONFLICT) {
int row = resolver.getRow();
crs.absolute(row);
int colCount = crs.getMetaData().getColumnCount();
for (int j = 1; j <= colCount; j++) {
if (resolver.getConflictValue(j) != null) {
crsValue = crs.getObject(j);
resolverValue = resolver.getConflictValue(j);
. . . // compare crsValue and resolverValue to determine the
// value to be persisted
resolvedValue = crsValue;
resolver.setResolvedValue(j, resolvedValue);
}
}
}
}
}
Note that the SyncResolver object uses key columns internally to identify specic
rows. If you do not set the key column(s) (using the CachedRowSet method setKeyColumns) the SyncResolver object will not be able to function correctly.
You can plug in an alternate provider simply by setting it as the provider. The
following line of code, in which the argument is the fully qualied class name of
a SyncProvider implementation, creates a CachedRowSet object initialized with the
specied provider.
CachedRowSet crs = new CachedRowSetImpl(
"com.fred.providers.HighAvailablityProvider");
Another option is to change the provider after a CachedRowSet object has been created, as is done in the following line of code.
crs.setSyncProvider("com.fred.providers.HighAvailablityProvider");
Notifying Listeners
Being a JavaBeans component means that a RowSet object can notify other components when certain things happen to it. For example, if data in a RowSet object
changes, the RowSet object can notify interested parties of that change. The nice
29
30
CACHEDROWSET
Setting Up Listeners
A listener for a RowSet object is a component that implements the following
methods from the RowSetListener interface:
cursorMoveddenes what the listener will do, if anything, when the cursor
in the RowSet object moves
rowChangeddenes what the listener will do, if anything, when one or
more column values in a row have changed, a row has been inserted, or a
row has been deleted
rowSetChangeddenes what the listener will do, if anything, when the
RowSet object has been populated with new data
An example of a component that might want to be a listener is a BarGraph object
that graphs the data in a RowSet object. As the data changes, the BarGraph object
can update itself to reect the new data.
As an application programmer, the only thing you need to do to take advantage
of the notication mechansim is to add or remove listeners. The following line of
code means that every time the cursor for crs moves, values in crs are changed, or
crs as a whole gets new data, the BarGraph object bar will be notied.
crs.addRowSetListener(bar);
You can also stop notications by removing a listener, as is done in the following
line of code.
crs.removeRowSetListener(bar);
In our Coffee Break scenario, lets assume that headquarters checks with the
database periodically to get the latest price list for the coffees it sells online. In
this case, the listener is the PriceList object priceList at the Coffee Break web site,
which must implement the RowSetListener methods cursorMoved, rowChanged, and
rowSetChanged. The implementation of cursorMoved could be to do nothing because
the position of the cursor does not affect priceList. The implementations for
rowChanged and rowSetChanged, on the other hand, need to specify what is to be
done to update priceList. Because the listener in this case is part of a Web service,
the implementations will probably send the latest data in a RowSet object in XML
format, which is effectively the standard format for Web services communica-
tions. The chapter WebRowSet, starting on page 77, shows an easy way to
send data in XML format.
After setting properties and setting the page size, you call the method execute or
populate. Because the page size has been set to 100, the method execute, used in the
following line of code, executes the command for crs and populates crs with the
rst 100 rows from the resulting ResultSet object.
crs.execute();
The method for getting subsequent rows is nextPage, which increments the current
page of crs, fetches the next 100 rows, and reads them into crs. You can use the
31
32
CACHEDROWSET
method nextPage in a while loop to get all of the rows because it will keep fetching
100 rows at a time until there are no more rows, at which time nextPage returns
false and ends the loop. The code fragment that follows uses a second while loop
within the rst one, which uses the method next to iterate through each row of
each page.
If, for example, you want to update the quantity for item 1235, you need to do
the work within the inner while loop to be sure that you will nd the row where
item 1235 is located. The following code iterates through each page until it nds
item 1235 and then updates its quantity. The code then calls the method updateRow to save the update to memory and the method acceptChanges to save the
update to the database.
crs.setPageSize(50);
crs.execute();
while(crs.next()) {
if (crs.getInt("ITEM_ID") == 1235) {
System.out.println("QUAN value: " + crs.getInt("QUAN"));
crs.updateInt("QUAN", 99);
crs.updateRow();
while(crs.nextPage()) {
System.out.println("Page number: " + i);
while(crs.next()) {
if (crs.getInt("ITEM_ID") == 1235) {
System.out.println("QUAN value: " + crs.getInt("QUAN"));
crs.updateInt("QUAN", 99);
crs.updateRow();
crs.acceptChanges();
}
}
i++;
}
crs.acceptChanges();
If you have reached the end of the data and want to go back through it in reverse,
you can use the method previousPage. This method decrements the number of the
current page and fetches the previous 50 rows (or whatever number the page size
is). You can go back through all the pages by putting previousPage in a while loop,
analogous to going forward through all the pages with the method nextPage in a
while loop.
33
CODE SAMPLE
Code Sample
This sample code demonstrates paging and using the method acceptChanges. Headquarters is sending an updated list of all the merchandise that individual Coffee
Break coffee houses can order. Because it is presumably very large, we will not
use the entire table in the example. For example purposes, we will use only
twelve rows and set the page size to 4, which means that there will be three
pages. The sample code does the following:
1. Creates the table MERCH_CATALOG and inserts data into it.The data types
for the columns in the table MERCH_CATALOG are:
ITEM_IDINTEGER
ITEM_NAMEVARCHAR(20)
SUP_IDINTEGER
PRICEDECIMAL(6,2)
Note that instead of using SQL INSERT statements, data is inserted into the
table programmatically. That is, after calling the method moveToInsertRow,
updater methods and insertRow are called to insert a row of data.
Table 32 MERCH_CATALOG
ITEM_ID
ITEM_NAME
SUP_ID
PRICE
00001234
Cup_Large
00456
5.99
00001235
Cup_Small
00456
2.99
00001236
Saucer
00456
2.99
00001287
Carafe
00456
25.99
00006931
Carafe
00927
44.99
00006935
PotHolder
00927
3.50
00006977
Napkin
00927
3.99
00006979
Towel
00927
4.99
00004488
CofMaker
08732
89.99
00004490
CofGrinder
08732
59.99
34
CACHEDROWSET
ITEM_ID
ITEM_NAME
SUP_ID
PRICE
00004495
EspMaker
08732
79.99
00006914
Cookbook
00927
15.00
2. Creates a CachedRowSet object and sets its properties so that it can make a connection to the database.
3. Populates the CachedRowSet object using the method execute. Uses paging to
send four rows at a time, which will require three CachedRowSet objects.
4. Updates the price of the small cup (ITEM_ID 1235) to: new BigDecimal("3.50").
5. Adds a new row for a new item. Values (0006914, "Tablecloth", 00927, new
BigDecimal("19.99").
6. Calls the method acceptChanges to update the database with the changes made in
4 and 5.
=========================================================
import java.sql.*;
import javax.sql.rowset.*;
import java.math.BigDecimal;
import com.sun.rowset.*;
public class CachedRowSetSample {
public static void main( String [] args) {
String strUrl = "jdbc:datadirect:oracle://" +
"129.158.229.21:1521;SID=ORCL9";
String strUserId = "scott";
String strPassword = "tiger";
String className = "com.ddtek.jdbc.oracle.OracleDriver";
CachedRowSet crs;
int i = 1;
try {
Class.forName(className);
} catch(java.lang.ClassNotFoundException e) {
System.err.print("ClassNotFoundException: ");
System.err.println(e.getMessage);
}
CODE SAMPLE
try {
Connection con = DriverManager.getConnection(
strUrl, strUserId, strPassword);
con.setAutoCommit(false);
Statement stmt = con.createStatement();
stmt.executeUpdate("drop table MERCH_INVENTORY");
stmt.executeUpdate("create table MERCH_INVENTORY( " +
"ITEM_ID INTEGER, ITEM_NAME VARCHAR(20), " +
"SUP_ID INTEGER, PRICE DECIMAL(6,2))");
PreparedStatement pStmt = con.prepareStatement(
"insert into MERCH_INVENTORY values(?, ?, ?, ?)");
// inserting values for 12 rows
pStmt.setInt(1, 1234);
pStmt.setString(2, "Cup_Large");
pStmt.setInt(3, 456);
pStmt.setBigDecimal(4, new BigDecimal("5.99"));
pStmt.executeUpdate();
pStmt.setInt(1, 1235);
pStmt.setString(2, "Cup_Small");
pStmt.setInt(3, 456);
pStmt.setBigDecimal(4, new BigDecimal("2.99"));
pStmt.executeUpdate();
pStmt.setInt(1, 1236);
pStmt.setString(2, "Saucer");
pStmt.setInt(3, 456);
pStmt.setBigDecimal(4, new BigDecimal("2.99"));
pStmt.executeUpdate();
pStmt.setInt(1, 1287);
pStmt.setString(2, "Carafe");
pStmt.setInt(3, 456);
pStmt.setBigDecimal(4, new BigDecimal("25.99"));
pStmt.executeUpdate();
pStmt.setInt(1, 6931);
pStmt.setString(2, "Carafe");
pStmt.setInt(3, 927);
pStmt.setBigDecimal(4, new BigDecimal("44.99"));
pStmt.executeUpdate();
pStmt.setInt(1, 6935);
pStmt.setString(2, "PotHolder");
pStmt.setInt(3, 927);
35
36
CACHEDROWSET
pStmt.setBigDecimal(4, new BigDecimal("3.50"));
pStmt.executeUpdate();
pStmt.setInt(1, 6977);
pStmt.setString(2, "Napkin");
pStmt.setInt(3, 927);
pStmt.setBigDecimal(4, new BigDecimal("3.99"));
pStmt.executeUpdate();
pStmt.setInt(1,6979);
pStmt.setString(2, "Towel");
pStmt.setInt(3, 927);
pStmt.setBigDecimal(4, new BigDecimal("4.99"));
pStmt.executeUpdate();
pStmt.setInt(1, 4488);
pStmt.setString(2, "CofMaker");
pStmt.setInt(3, 8372);
pStmt.setBigDecimal(4,new BigDecimal("89.99"));
pStmt.executeUpdate();
pStmt.setInt(1, 4490);
pStmt.setString(2, "CofGrinder");
pStmt.setInt(3, 8732);
pStmt.setBigDecimal(4, new BigDecimal("59.99"));
pStmt.executeUpdate();
pStmt.setInt(1, 4495);
pStmt.setString(2, "EspMaker");
pStmt.setInt(3, 8732);
pStmt.setBigDecimal(4, new BigDecimal("79.99"));
pStmt.executeUpdate();
pStmt.setInt(1, 6914);
pStmt.setString(2, "Cookbook");
pStmt.setInt(3, 927);
pStmt.setBigDecimal(4, new BigDecimal("15.00"));
pStmt.executeUpdate();
con.commit();
con.close();
crs = new CachedRowSetImpl();
crs.setUrl(strUrl);
crs.setUsername(strUserId);
crs.setPassword(strPassword);
crs.setCommand("select * from MERCH_CATALOG");
CODE SAMPLE
37
38
CACHEDROWSET
crs.acceptChanges();
crs.close();
} catch( SQLException sqle) {
System.out.println("SQLException caught: " + sqle.getMessage());
}
} // End of main
} // End of class
4
JoinRowSet
A
The variable jrs has the default properties that a default CachedRowSet object has,
but it has no data until RowSet objects are added to it.
39
40
JOINROWSET
In the world of RowSet technology, you can accomplish the same result without
having to send a query to the data source. You can add RowSet objects containing
the data in the two tables to a JoinRowSet object. Then, because all the pertinent
data is in the JoinRowSet object, you can perform a query on it to get the desired
data.
The following code fragments create two CachedRowSet objects, coffees populated
with the data from the table COFFEES, and suppliers populated with the data from
the table SUPPLIERS. The readers (RowSetReader objects) for coffees and suppliers
have to make a connection to the database to execute their commands and get
populated with data, but once that is done, they do not have to reconnect again in
order to form a JOIN. You can form any number of JOIN relationships from these
two RowSet objects using jrs while it is disconnected.
CachedRowSet coffees = new CachedRowSetImpl();
coffees.setCommand("SELECT * FROM COFFEES");
coffees.setUsername(name);
coffees.setPassword(password);
coffees.setURL("jdbcDriverURL1");
coffees.execute();
CachedRowSet suppliers = new CachedRowSetImpl();
suppliers.setCommand("SELECT * FROM SUPPLIERS");
suppliers.setUsername(name);
suppliers.setPassword(password);
suppliers.setURL("jdbcDriverURL2");
suppliers.execute();
The contents of these two tables are shown in Table 41 and Table 42. Note that
for this example, the tables have different columns than in some other examples
to make displaying the JOIN result (Table 43 on page 48) more manageable..
Table 41 COFFEES
COF_ID
COF_NAME
SUP_ID
PRICE
1250
Colombian
101
7.99
1300
French_Roast
49
8.99
1800
Espresso
150
10.99
2250
Colombian_Decaf
101
8.99
1000
House_Blend
49
7.99
2000
House_Blend_Decaf
49
8.99
Table 42 SUPPLIERS
SUP_ID
SUP_NAME
ADDRESS
101
Acme, Inc.
Groundsville1
49
Superior Coffee
Mendocino1
41
42
JOINROWSET
Table 42 SUPPLIERS
SUP_ID
SUP_NAME
ADDRESS
150
The High
Ground
Meadows1
Looking at the SUPPLIERS table, you can see that Acme, Inc. has an identication
number of 101. The coffees in the table COFFEES with the supplier identication
number of 101 are Colombian and Colombian_Decaf. The joining of information from both tables is possible because the two tables have the column SUP_ID
in common. In JDBC RowSet technology, SUP_ID, the column on which the JOIN
is based, is called the match column.
Each RowSet object added to a JoinRowSet object must have a match column, the
column on which the JOIN is based. There are two ways to set the match column
for a RowSet object. The rst way is to pass the match column to the JoinRowSet
method addRowSet. The second way is to use the Joinable method setMatchColumn.
jrs.addRowSet(coffees, 3);
coffees, being the rst RowSet object to be added to jrs, now forms the basis for the
JOIN relationship, meaning that any RowSet object added to jrs must have SUP_ID
USING JOINABLE.SETMATCHCOLUMN
You can pass the method addRowSet the column name of the match column rather
than the column number if you like.
jrs.addRowSet(coffees, "SUP_ID");
The next RowSet object added to jrs is suppliers, which can be added because it also
has the column SUP_ID. The following line of code adds suppliers to jrs and sets
the column SUP_ID as the match column.
jrs.addRowSet(suppliers, 1);
or
jrs.addRowSet(suppliers, "SUP_ID");
Using Joinable.setMatchColumn
A second way to set a match column is to use the Joinable method setMatchColumn.
All RowSet interfaces are subinterfaces of the Joinable interface, meaning that they
implement the Joinable interface. Accordingly, all ve of the RowSet implementations implement the Joinable interface. Therefore, any RowSet object created from
one of the constructors in the reference implementation has the ability to call
Joinable methods. The following lines of code, in which crs is a CachedRowSet
object, set the match column for crs and then add it to the JoinRowSet object jrs.
(Joinable)crs.setMatchColumn(1);
JoinRowSet jrs = new JoinRowSetImpl();
jrs.addRowSet(crs);
The Joinable interface provides methods for setting match columns, getting match
columns, and unsetting match columns.
43
44
JOINROWSET
match column has already been set, the method addRowSet takes only the RowSet
object to be added as an argument.
int [] matchCols = {1, 3};
crs.setMatchColumn(matchCols);
jrs.addRowSet(crs);
The following two lines dene an array of String objects and set that array as the
match column.
String [] matchCols = {"SUP_ID", "ADDRESS"};
crs.setMatchColumn(matchCols);
jrs.addRowSet(crs);
You can also use the method addRowSet to set multiple columns as the match column when no match column has been set previously.
int [] matchCols = {1, 3};
jrs.addRowSet(crs, matchCols);
or
String [] matchCols = {"SUP_ID", "ADDRESS"};
jrs.addRowSet(crs, matchCols);
CODE SAMPLE
The JoinRowSet interface provides constants for setting the type of JOIN that will
be formed, but currently the only type that is implemented is JoinRowSet.INNER_JOIN.
Code Sample
The following code sample combines code from throughout the chapter into a
program you can run after you substitute the appropriate information for the
variables url, userId, passWord, and strDriver.
This sample code demonstrates using a JoinRowSet object to perform a JOIN on the
tables COFFEES and SUPPLIERS based on SUP_ID as the match column. This code
sets the match column by supplying the column name to the method addRowSet.
=========================================================
import java.sql.*;
import javax.sql.rowset.*;
import com.sun.rowset.*;
import java.math.BigDecimal;
public class JoinRowSetSample {
public static void main(String []args) {
String strUrl = "jdbc:datadirect:oracle://" +
"129.158.229.21:1521;SID=ORCL9";
String strUserId = "scott";
tring strPassword = "tiger";
String className = "com.ddtek.jdbc.oracle.OracleDriver";
BigDecimal b;
try {
Class.forName(className);
} catch(java.lang.ClassNotFoundException e) {
System.err.print("ClassNotFoundException: ");
System.err.println(e.getMessage());
}
try {
45
46
JOINROWSET
Connection con = DriverManager.getConnection(
strUrll, strUserId, strPassWord);
con.setAutoCommit(false);
Statement stmt = con.createStatement();
stmt.addBatch("drop table COFFEES");
stmt.addBatch("drop table SUPPLIERS");
stmt.addBatch("create table COFFEES(COF_ID INTEGER, " +
"COF_NAME VARCHAR(20), SUP_ID INTEGER, " +
"PRICE DECIMAL(6,2))");
b = new BigDecimal("7.99");
stmt.addBatch("insert into COFFEES values " +
"(1250, Colombian, 101, "+b+")");
b = new BigDecimal("8.99");
stmt.addBatch("insert into COFFEES values " +
"(1300, French_Roast, 49, "+b+")");
b = new BigDecimal("10.99");
stmt.addBatch("insert into COFFEES values " +
"(1800, Espresso, 150, "+b+")");
b = new BigDecimal("8.99");
stmt.addBatch("insert into COFFEES values " +
"(2250, Colombian_Decaf, 101, "+b+")");
b = new BigDecimal("7.99");
stmt.addBatch("insert into COFFEES values " +
"(1000, House_Blend, 491, "+b+")");
b = new BigDecimal("8.99");
stmt.addBatch("insert into COFFEES values " +
"(2000, House_Blend_Decaf, 49, "+b+"");
stmt.addBatch("create table SUPPLIERS" +
"(SUP_ID INTEGER, SUP_NAME VARCHAR(40), " +
"ADDRESS VARCHAR(60))");
stmt.addBatch("insert into SUPPLIERS values " +
"(101, Acme, Inc., Groundsville, CA 95199");
stmt.addBatch("insert into SUPPLIERS values " +
"(49, Superior Coffee, Mendocino, CA 95460");
stmt.addBatch("insert into SUPPLIERS values " +
"(150, The High Ground, Meadows, CA 93966");
CODE SAMPLE
stmt.executeBatch();
con.commit();
ResultSet rs1 = stmt.executeQuery(
"select * from COFFEES");
ResultSet rs2 = stmt.executeQuery(
"select * from SUPPLIERS");
// Populate two CachedRowSet objects and add them to a JoinRowSet object
CachedRowSet coffees = new CachedRowSetImpl();
coffees.populate(rs1);
System.out.print("First CachedRowSet size: ");
System .out.println(coffees.size());
CachedRowSet suppliers = new CachedRowSetImpl();
suppliers.populate(rs2);
System.out.print("Second CachedRowSet size: ");
System .out.println(suppliers.size());
con.close();
JoinRowSet jrs = new JoinRowSetImpl();
jrs.addRowSet(coffees, "SUP_ID");
jrs.addRowSet(suppliers, "SUP_ID");
System.out.print("Size of the JoinRowSet is: ");
System .out.println(jrs.size());
System.out.println("Contents are ");
while(jrs.next()) {
System.out.print("COF_ID: ");
System.out.println( jrs.getInt("COF_ID"));
System.out.print("COF_NAME: " );
System.out.println( jrs.getString("COF_NAME"));
System.out.print("PRICE: " );
System.out.println(jrs.getGetBigDecimal("PRICE"));
System.out.print("SUP_ID: " );
System.out.println( jrs.getInt("SUP_ID"));
System.out.print("SUP_NAME: ");
System.out.println( jrs.getString("SUP_NAME"));
System.out.print("ADDRESS: ");
System.out.println(jrs.getString("ADDRESS"));
}
jrs.close();
} catch(SQLException sqle) {
System.out.println("Caught SQLException: "
47
48
JOINROWSET
+ sqle.getMessage());
}
}
}
Table 43 represents the results of the inner JOIN of the tables COFFEES and SUPPLIERS.
COF_NAME
PRICE
SUP_ID
SUP_NAME
ADDRESS
1250
Colombian
7.99
101
Acme, Inc.
Groundsville, CA 95199
1300
French_Roast
8.99
49
Superior Coffee
Mendocino, CA 95460
1800
Espresso
10.99
150
Meadows, CA 93966
2250
Colombian_Decaf
8.99
101
Acme, Inc.
Groundsville, CA 95199
1000
House_Blend
7.99
49
Superior Coffee
Mendocino, CA 95460
2000
House_Blend_Decaf
8.99
49
Superior Coffee
Mendocino, CA 95460
5
FilteredRowSet
A
FilteredRowSet object lets you cut down the number of rows that are visible in
a RowSet object so that you can work with only the data that is relevant to what
you are doing. You decide what limits you want to set on your data (how you
want to lter the data) and apply that lter to a FilteredRowSet object. In other
words, the FilteredRowSet object makes visible only the rows of data that t within
the limits you set. A JdbcRowSet object, which always has a connection to its data
source, can do this ltering with a query to the data source that selects only the
columns and rows you want to see. The querys WHERE clause denes the ltering criteria. A FilteredRowSet object provides a way for a disconnected RowSet
object to do this ltering without having to execute a query on the data source,
thus avoiding having to get a connection to the data source and sending queries
to it.
For example, assume that the Coffee Break chain of coffee houses has grown to
hundreds of stores throughout the country, and all of them are listed in a table
called COFFEE_HOUSES. The owner wants to measure the success of only the
stores in California using his laptop, which cannot make a connection to his
database system. This comparison will look at the protability of selling merchandise versus selling coffee drinks plus various other measures of success, and
it will rank California stores by coffee drink sales, merchandise sales, and total
sales. Because the table COFFEE_HOUSES has hundreds of rows, these comparisons will be faster and easier if the amount of data being searched is cut down to
only those rows where the value in the column STORE_ID indicates California.
49
50
FILTEREDROWSET
This is exactly the kind of problem that a FilteredRowSet object addresses by providing the following capabilities:
Ability to limit the rows that are visible according to set criteria
Ability to select which data is visible without being connected to a data
source
In this chapter you will walk through how to do the following:
Dene ltering criteria in a Predicate object
Create a FilteredRowSet object and set it with a Predicate object
Set a FilteredRowSet object with a second Predicate object to lter data even
further
Update a FilteredRowSet object
Remove all lters so that all rows are once again visible
51
The table listing all of the coffee houses, named COFFEE_HOUSES, has hundreds
of rows. To make things more manageable, only part of the table is shown in this
example, but it is enough so that you can see how it is set up and how the ltering is done.
A value in the column STORE_ID is an int that indicates, among other things, the
state in which the coffee house is located. A value beginning with 10, for example, means that the state is California. STORE_ID values beginning with 32 indicate Oregon, and those beginning with 33 indicate the state of Washington. Table
51 shows an abbreviated version of the table COFFEE_HOUSES.
Table 51 COFFEE_HOUSES
STORE_ID
CITY
COFFEE
MERCH
TOTAL
10023
Mendocino
3450.55
2005.21
5455.76
33002
Seattle
4699.39
3109.03
7808.42
10040
SF
5386.95
2841.27
8228.22
32001
Portland
3147.12
3579.52
6726.64
10042
SF
2863.35
1874.62
4710.97
10024
Sacramento
1987.77
2341.21
4328.98
10039
Carmel
2691.69
1121.21
3812.90
10041
LA
1533.48
1007.02
2540.50
33002
Olympia
2733.83
1550.48
4284.31
33010
Seattle
3210.22
2177.48
5387.70
10035
SF
1922.85
1056.91
2979.76.
10037
LA
2143.38
1876.66
4020.04
10034
San_Jose
1234.55
1032.99
2267.54
32004
Eugene
1356.03
1112.81
2468.84
10041
LA
2284.46
1732.97
4017.43
52
FILTEREDROWSET
This is a very simple implementation that checks the value in the column
STORE_ID in the current row of the given RowSet object to see if it is in the prescribed range.
The following line of code creates a Filter1 object that tests whether the value in
the column STORE_ID is within the range of 10000 to 10999, inclusive.
Filter1 range = new Filter1(10000, 10999, "STORE_ID");
Note that the Filter1 object just dened applies to one column. It is possible to
have it apply to two or more columns by making each of the parameters arrays
instead of single values. For example, the constructor for a Filter object could
look like the following:
public Filter2(Object [] lo, Object [] hi, Object [] colNumber) {
this.lo = lo;
this.hi = hi;
this.colNumber = colNumber;
}
The rst element in colNumber gives the rst column in which the value will be
checked against the rst element in lo and the rst element in hi. The value in the
second column indicated by colNumber will be checked against the second elements in lo and hi, and so on. Therefore, the number of elements in the three
arrays should be the same. Here is what an implementation of the method evaluate(RowSet rs) might look like for a Filter2 object, in which the parameters are
arrays.
public boolean evaluate(RowSet rs) {
CachedRowSet crs = (CachedRowSet)rs;
boolean bool1 = true;
boolean bool2 = false;
for (int i = 0; i < colNumber.length; i++) {
if ((rs.getObject(colNumber[i] >= lo [i]) &&
(rs.getObject(colNumber[i] <= hi[i]) {
return bool1;
} else {
return bool2;
}
}
The advantage of using a Filter2 implementation is that you can use parameters of
any Object type and can check one column or multiple columns without having to
write another implementation. However, you must pass an Object type, which
means that you must convert a primitive type to its Object type. For example, if
you use an int for lo and hi, you must convert the int to an Integer object before
passing it to the constructor. String objects are already an Object type, so you do
not have to convert them. The following line of code creates an Integer object that
you could pass to the constructor for a Filter2 object.
Integer loInt = new Integer("10000");
53
54
FILTEREDROWSET
The implementation extends the BaseRowSet abstract class, so frs has the default
properties dened in BaseRowSet. This means that frs is scrollable, updatable, does
not show deleted rows, has escape processing turned on, and so on. Also,
because the FilteredRowSet interface is a subinterface of CachedRowSet, Joinable, and
WebRowSet, frs has the capabilities of each. It can operate as a disconnected RowSet
object, can be part of a JoinRowSet object, and can read and write itself in XML
format using the RIXmlProvider.
Like other disconnected RowSet objects, frs must populate itself with data from a
tabular data source, which is a relational database in the reference implementation. The following code fragment sets the properties necessary to connect to a
database to execute its command. As pointed out earlier, code in this tutorial uses
the DriverManager class to make a connection, which is done for convenience.
Normally, it is better to use a DataSource object that has been registered with a
naming service that implements the Java Naming and Directory Interface
(JNDI).
frs.setCommand("SELECT * FROM COFFEE_HOUSES");
frs.setURL("jdbc:mySubprotocol:myDatabase");
frs.setUsername("Vladimir");
frs.setPassword("secret");
The following line of code populates frs with the data stored in the
COFFEE_HOUSE table.
frs.execute();
Remember that the method execute does all kinds of things behind the scenes by
calling on the RowSetReader object for frs, which creates a connection, executes
the command for frs, populates frs with the data from the ResultSet object that is
produced, and closes the connection. Note that if the table COFFEE_HOUSES had
more rows than frs could hold in memory at one time, the CachedRowSet paging
methods would have been used. If you need a refresher on paging or the method
execute, see the chapter CachedRowSet, starting on page 19.
In our scenario, the Coffee Break owner would have done the preceding tasks in
the ofce and then downloaded frs to his laptop. From now on, frs will operate
independently, without the benet of a connection to the data source.
The next line of code sets range as the lter for frs.
frs.setFilter(range);
To do the actual ltering, you call the method next, which in the reference implementation calls the appropriate version of the Predicate method implement behind
the scenes. [Add material on three evaluate methods.]
If the return value is true, the row will be visible; if the return value is false, it will
not. Thus, the following code fragment iterates through frs and prints only the
rows in which the store is in California.
while (frs.next()) {
int storeId = frs.getInt("STORE_ID");
String city = frs.getString("CITY");
long cof = frs.getLong("COF_SALES");
long merch = frs.getLong("MERCH_SALES");
long total = frs.getLong("TOTAL_SALES");
print(storeId + "
")
print(city + "
")
print(cof + "
")
print(merch + "
")
println(total );
}
If none of the rows satised the criteria in range, there would have been no visible
rows and nothing would have been printed.
55
56
FILTEREDROWSET
The following code fragment sets the new lter and iterates through the rows in
frs, printing out the rows where the CITY column contains either SF or LA. Note
that frs currently contains only rows where the store is in California, so the criteria of the Predicate object state are still in effect when the lter is changed to
another Predicate object. The code that follows sets the lter to the CityFilter object
city. The CityFilter implementation uses arrays as parameters to the constructors to
illustrate how that can be done.
Object [] city1 = {"SF"};
Object [] city2 = {"LA"};
Object [] colName = {"CITY"};
CityFilter city = new CityFilter(city1, city2, colName);
frs.setFilter(city);
while (frs.next()) {
int storeId = frs.getInt("STORE_ID");
String city = frs.getString("CITY");
BigDecimal cof = frs.getBigDecimal("COF_SALES");
BigDecimal merch = frs.getBigDecimal("MERCH_SALES");
BigDecimal total = frs.getBigDecimal("TOTAL_SALES");
System.out.print(storeId + "
");
System.out.print(city + "
");
System.out.print(cof + "
");
System.out.print(merch + "
");
System.out.println(total );
}
The printout should contain a row for each store that is in San Francisco, California or Los Angeles, California. If there were a row in which the CITY column
contained LA and the STORE_ID column contained 40003, it would not be included
in the list because it had already been ltered out when the lter was set to range.
(40003 is not in the range of 10000 to 10999.)
57
58
FILTEREDROWSET
If you were to iterate through frs using the method next, you would nd a row for
the new coffee house in San Francisco, California, but not for the store in San
Francisco, Washington.
DELETING A ROW
The owner can add the store in Washington by nullifying the lter. With no lter
set, all rows in frs are once more visible, and a store in any location can be added
to the list of stores. The following line of code unsets the current lter, effectively nullifying both of the Predicate implementations previously set on frs.
frs.setFilter(null);
Deleting a Row
If the owner decides to close down or sell one of the Coffee Break coffee houses,
he will want to delete it from the COFFEE_HOUSES table. He can delete the row
for the underperforming coffee house as long as it is visible.
Given that the method setFilter has just been called with the argument null, there is
no lter set on frs. This means that all rows are visible and can therefore be
deleted. However, after the Filter1 object state, which ltered out any state other
than California, was set, only stores located in California could be deleted. When
the CityFilter object city was set for frs, only coffee houses in San Francisco, California or Los Angeles, California could be deleted because they were in the only
rows visible.
59
60
FILTEREDROWSET
public boolean evaluate(RowSet rs) {
boolean bool1 = false;
boolean bool2 = false ;
try { CachedRowSet crs = (CachedRowSet)rs;
// Check the present row todetermine if it lies
// within the filtering criteria.
for (int i = 0; i < idx.length; i++) {
if ( ((rs.getObject(idx[i]).toString()).compareTo(lo[i].toString()) < 0) ||
((rs.getObject(idx[i]).toString()).compareTo(hi[i].toString()) > 0) ) {
bool2 = true; // outside filter constraints
} else {
bool1 = true; // within filter constraints
}
}
} catch( SQLException e) {
}
if (bool2) {
return false;
} else {
return true;
}
}
// implementation for two other versions of evaluate not shown
}
Code Samples
This section has three code samples to illustrate using three different Predicate
objects as lters for a FilteredRowSet object. In each case, the code for the Predicate
object follows the code for the FilteredRowSet object. As with all the code samples,
you need to substitute your own JDBC URL, user name, password, and Driver
class name.
CODE SAMPLE 1
Code Sample 1
The rst code sample shows how to lter out rows in which the value in the designated column does not fall within the specied range. This is a the basic case
for a FilteredRowSet object. The Predicate object used as the lter for
FilteredRowSetSample1 is Range1, which follows the code sample.
You will see the following coding features in this sample code.
The code for creating and inserting values into the table COFFEE_HOUSES
uses the method addBatch to add SQL statements to the list of statements
that the Statement object maintains. All of the statements in the list are sent
to the database as a single unit (a batch) when the method executeBatch is
called. Sending all of the statements as a batch is more efcient that sending each statement to the database separately.
The method Connection.setAutoCommit is called to turn off auto-commit
mode. With auto-commit mode off, none of the statements is executed until
the method executeBatch is called. Then the method con.commit is called to
commit the batch as one transaction. This is another efciency strategy.
Executing all of the statements in one transaction is more efcient that executing each statement separately.
Because con is still an open Connection object, it is passed to the method execute. The reader will use this connection instead of having to obtain a new
one, as is the case when no connection is passed to the method execute.
The method close is called on the connection. This is necessary because the
reader does not close the connection if it is passed one. The reader closes
a connection only if it created the connection.
The FilteredRowSet object is closed. It is good coding practice to close the
objects you have created.
Note that some drivers may not support sending statements to the database as a
batch. You can check by calling the DatabaseMetaData method supportsBatchUpdates,
which returns true if the driver supports batch updates. If your driver returns false,
you will need to rewrite the code to send each SQL statement as a separate Statement object.
stmt.executeUpdate("insert into COFFEE_HOUSES values(.....)");
You can still have all of the statements executed as one transaction with autocommit mode set to false. Instead of calling the method executeBatch, you will need
to call the method con.commit to execute the Statement objects.
61
62
FILTEREDROWSET
========================================================
import java.sql.*;
import javax.sql.rowset.*;
import com.sun.rowset.*;
import java.math.BigDecimal;
public class FilteredRowSetSample1 {
public static void main(String [] args) {
Connection con;
String strUrl = "jdbc:datadirect:oracle://" +
"129.158.229.21:1521;SID=ORCL9";
String strUserId = "scott";
tring strPassword = "tiger";
String className = "com.ddtek.jdbc.oracle.OracleDriver";
BigDecimal b1;
BigDecimal b2;
BigDecimal b3;
try {
// Load the class of the driver
Class.forName(className);
} catch(java.lang.ClassNotFoundException e) {
System.err.print("ClassNotFoundException: ");
System.err.println(e.getMessage());
}
try {
con = DriverManager.getConnection(
strUrl, strUserId, strPassword);
con.setAutoCommit(false);
Statement stmt = con.createStatement();
stmt.addBatch("drop table Coffee_Houses");
stmt.addBatch("create table Coffee_Houses(" +
"store_id int, city varchar(20), coffee " +
"decimal(6,2), merch decimal(6,2), " +
"total decimal(6,2))");
b1 = new BigDecimal("3450.55");
b2 = new BigDecimal("2005.21");
b3 = new BigDecimal("5455.76");
stmt.addBatch("insert into Coffee_Houses " +
"values(10023, Mendocino, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("4699.39");
CODE SAMPLE 1
b2 = new BigDecimal("3109.03");
b3 = new BigDecimal("7808.42");
stmt.addBatch("insert into Coffee_Houses " +
"values(33002, Seattle, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("5386.95");
b2 = new BigDecimal("2841.27");
b3 = new BigDecimal("8228.22");
stmt.addBatch("insert into Coffee_Houses " +
"values(100040, SF, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("3147.12");
b2 = new BigDecimal("3579.52");
b3 = new BigDecimal("6726.64");
stmt.addBatch("insert into Coffee_Houses " +
"values(32001,Portland, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("2863.35");
b2 = new BigDecimal("1874.62");
b3 = new BigDecimal("4710.97");
stmt.addBatch("insert into Coffee_Houses " +
"values(10042,SF, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("1987.77");
b2 = new BigDecimal("2341.21");
b3 = new BigDecimal("4328.98");
stmt.addBatch("insert into Coffee_Houses " +
"values(10024, Sacramento, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("2692.69");
b2 = new BigDecimal("1121.21");
b3 = new BigDecimal("8312.90");
stmt.addBatch("insert into Coffee_Houses " +
"values(10039,Carmel, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("1533.48");
b2 = new BigDecimal("1007.02");
b3 = new BigDecimal("2450.50");
stmt.addBatch("insert into Coffee_Houses " +
"values(10041,LA, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("2733.83");
b2 = new BigDecimal("1550.48");
b3 = new BigDecimal("4284.31");
stmt.addBatch("insert into Coffee_Houses " +
"values(33002,Olympia, "+b1+", "+b2+", "+b3+")");
stmt.executeBatch();
63
64
FILTEREDROWSET
con.commit();
// Now all the data has been inserted into the DB.
// Create a FilteredRowSet object, set the properties and
// populate it with this data.
FilteredRowSet frs = new FilteredRowSetImpl();
frs.setUsername(strUserId);
frs.setPassword(strPassword);
frs.setUrl(strUrl);
frs.setCommand("select * from Coffee_Houses");
frs.execute(con);
con.close();
Range1 stateFilter = new Range1(10000, 10999, 1);
frs.setFilter(stateFilter);
while(frs.next()) {
System.out.println("Store id is: " + frs.getInt(1));
}
// Now try to insert a row that does not satisfy the criteria.
// An SQLException will be thrown.
try {
frs.moveToInsertRow();
frs.updateInt(1, 22999);
frs.updateString(2, "LA");
frs.updateBigDecimal(3, new BigDecimal("4455.01"));
frs.updateBigDecimal(4, new BigDecimal("1579.33"));
frs.updateBigDecimal(5, new BigDecimal("6034.34"));
frs.insertRow();
frs.moveToCurrentRow();
} catch(SQLException sqle) {
System.out.print("A row that does not satisfy ");
System.out.println("the filter is being inserted");
System.out.println("Message: " + sqle.getMessage());
}
frs.close();
} catch(Exception e ) {
System.err.print("Caught unexpected Exception: ");
System.err.println(+ e.getMessage());
}
}
}
CODE SAMPLE 1
=========================================================
What follows is Range1, the implementation of the Predicate interface used in
FilteredRowSetSample1. This implementation checks whether a given value is in the
range from 10000 to 10999, which is the range for a STORE_ID number indicating that the store is in California.
import javax.sql.rowset.*;
import com.sun.rowset.*;
import java.util.*;
import java.lang.*;
import java.sql.*;
import javax.sql.RowSet;
import java.io.*;
public class Range1 implements Predicate, Serializable {
private int idx;
private int hi;
private int lo;
private String colName;
public Range1(int lo, int hi, int idx) {
this.hi = hi;
this.lo = lo;
this.idx = idx;
colName = new String("");
}
public Range1(int lo, int hi, String colName, int idx) {
this.lo = lo;
this.hi = hi;
this.colName = colName;
this.idx = idx;
}
public boolean evaluate(RowSet rs) {
int comp;
int columnVal = 0;
boolean bool = false;
CachedRowSetImpl crs = (CachedRowSetImpl) rs;
try {
columnVal = crs.getInt(idx);
if(columnVal <= hi && columnVal >= lo) {
bool = true;
} else {
bool = false;
65
66
FILTEREDROWSET
}
} catch(SQLException e) {
}
return bool;
}
public boolean evaluate(Object value, String columnName) {
int colVal;
boolean bool = false;
if(columnName.equals(colName)) {
colVal = (Integer.parseInt(value.toString()));
if( colVal <= hi && colVal >= lo) {
bool = true;
} else {
bool = false;
} else {
bool = true;
}
return bool;
}
public boolean evaluate(Object value, int columnIndex) {
int colVal;
boolean bool = false;
if(columnIndex == idx) {
colVal = (Integer.parseInt(value.toString()));
if( colVal <= hi && colVal >= lo) {
bool = true;
} else {
bool = false;
}
} else {
bool = true;
}
return bool;
}
}
Code Sample 2
This code sample shows how to set one lter and then another lter to get the
effect of both lters. The code creates the table COFFEE_HOUSES (just as
FilteredRowSetSample1 did), sets a Range1 object as the rst lter (just as
FilteredRowSetSample1 did), and then sets a Range2 object as the second lter. Range2
CODE SAMPLE 2
lters for coffee houses in the city of San Francisco ("SF" in the table). Finally,
the code prints the STORE_ID and CITY values for the rows visible in the FilteredRowSet object frs.
========================================================
import java.sql.*;
import javax.sql.rowset.*;
import com.sun.rowset.*;
import java.math.BigDecimal;
public class FilteredRowSetSample2 {
public static void main(String [] args) {
Connection con;
String strUrl = "jdbc:datadirect:oracle://" +
"129.158.229.21:1521;SID=ORCL9";
String strUserId = "scott";
tring strPassword = "tiger";
String className = "com.ddtek.jdbc.oracle.OracleDriver";
BigDecimal b1;
BigDecimal b2;
BigDecimal b3;
try {
// Load the class of the driver
Class.forName(className);
} catch(java.lang.ClassNotFoundException e) {
System.err.print("ClassNotFoundException: ");
System.err.println(e.getMessage());
}
try {
con = DriverManager.getConnection(
strUrl, strUserId, strPassword);
con.setAutoCommit(false);
Statement stmt = con.createStatement();
stmt.addBatch("drop table Coffee_Houses");
stmt.addBatch("create table Coffee_Houses(" +
"store_id int, city varchar(20), coffee " +
"decimal(6,2), merch decimal(6,2), " +
"total decimal(6,2))");
b1 = new BigDecimal("3450.55");
b2 = new BigDecimal("2005.21");
b3 = new BigDecimal("5455.76");
67
68
FILTEREDROWSET
stmt.addBatch("insert into Coffee_Houses " +
"values(10023, Mendocino, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("4699.39");
b2 = new BigDecimal("3109.03");
b3 = new BigDecimal("7808.42");
stmt.addBatch("insert into Coffee_Houses " +
"values(33002, Seattle, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("5386.95");
b2 = new BigDecimal("2841.27");
b3 = new BigDecimal("8228.22");
stmt.addBatch("insert into Coffee_Houses " +
"values(100040, SF, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("3147.12");
b2 = new BigDecimal("3579.52");
b3 = new BigDecimal("6726.64");
stmt.addBatch("insert into Coffee_Houses " +
"values(32001,Portland, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("2863.35");
b2 = new BigDecimal("1874.62");
b3 = new BigDecimal("4710.97");
stmt.addBatch("insert into Coffee_Houses " +
"values(10042,SF, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("1987.77");
b2 = new BigDecimal("2341.21");
b3 = new BigDecimal("4328.98");
stmt.addBatch("insert into Coffee_Houses " +
"values(10024, Sacramento, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("2692.69");
b2 = new BigDecimal("1121.21");
b3 = new BigDecimal("8312.90");
stmt.addBatch("insert into Coffee_Houses " +
"values(10039,Carmel, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("1533.48");
b2 = new BigDecimal("1007.02");
b3 = new BigDecimal("2450.50");
stmt.addBatch("insert into Coffee_Houses " +
"values(10041,LA, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("2733.83");
b2 = new BigDecimal("1550.48");
b3 = new BigDecimal("4284.31");
CODE SAMPLE 2
stmt.addBatch("insert into Coffee_Houses " +
"values(33002,Olympia, "+b1+", "+b2+", "+b3+")");
stmt.executeBatch();
con.commit();
// Now all the data has been inserted into the DB.
// Create a FilteredRowSet, set the properties and
// populate it with this data.
FilteredRowSet frs = new FilteredRowSetImpl();
frs.setUsername(strUserId);
frs.setPassword(strPassword);
frs.setUrl(strUrl);
frs.setCommand("select * from Coffee_Houses");
frs.execute(con);
con.close();
// Now create the filter and set it. Range 1 is the
// class that implements the Predicate interface.
Range1 stateFilter= new Range1(10000,10999,1);
frs.setFilter(stateFilter);
// Set the second filter to filter out the coffee houses
// so that only those in the city of San Francisco
// are visible.
Range2 cityFilter = new Range2("SF", "SF", 2);
frs.setFilter(cityFilter);
// Now only those stores whose store ID is between
// 10000 and 10999 and whose city value is SF
// are displayed
while(frs.next()) {
System.out.println("Store ID is: " + frs.getInt(1));
System.out.print("City is: " );
System.out.println( frs.getString(2));
}
frs.close();
} catch(Exception e ) {
System.err.print("Caught unexpected Exception: ");
System.err.println(+ e.getMessage());
}
}
}
69
70
FILTEREDROWSET
CODE SAMPLE 2
return false;
}
comp = columnVal.compareTo(hi);
//System.out.println("comp2 :"+comp);
if(comp > 0) {
return false;
}
} catch(SQLException e) {
} //end catch
return true;
}
public boolean evaluate(Object value, String columnName) {
int comp;
if(!(columnName.equals(colName))) {
return true;
}
comp = (value.toString()).compareTo(lo);
if ( comp < 0 ) {
return false;
}
comp = (value.toString()).compareTo(hi);
if ( comp > 0 ) {
return false;
}
return true;
}
public boolean evaluate(Object value, int columnIndex) {
int comp;
if(columnIndex != idx) {
return true;
}
comp = (value.toString()).compareTo(lo);
if( comp < 0 ) {
return false;|
}
comp = (value.toString()).compareTo(hi);
if ( comp > 0 ) {
return false;
}
return true;
}
}
71
72
FILTEREDROWSET
Code Sample 3
FilteredRowSetSample3 uses a Predicate object that has two ltering criteria combined
into one lter. In order to accommodate two sets of criteria, the arguments for the
constructors are arrays.
Predicate
==============================================================
import java.sql.*;
import javax.sql.rowset.*;
import com.sun.rowset.*;
import java.math.BigDecimal;
public class FilteredRowSetSample3 {
public static void main(String [] args) {
Connection con;
String strUrl = "jdbc:datadirect:oracle://" +
"129.158.229.21:1521;SID=ORCL9";
String strUserId = "scott";
tring strPassword = "tiger";
String className = "com.ddtek.jdbc.oracle.OracleDriver";
BigDecimal b1;
BigDecimal b2;
BigDecimal b3;
int [] idxArray = {1, 2};
Object [] loArray = {new Integer(10000), "SF"};
Object [] hiArray = {new Integer(10999), "SF"};
try {
// Load the class of the driver
Class.forName(className);
} catch(java.lang.ClassNotFoundException e) {
System.err.print("ClassNotFoundException: ");
System.err.println(e.getMessage());
}
try {
con = DriverManager.getConnection(
strUrl, strUserId, strPassword);
con.setAutoCommit(false);
Statement stmt = con.createStatement();
stmt.addBatch("drop table Coffee_Houses");
stmt.addBatch("create table Coffee_Houses(" +
CODE SAMPLE 3
"store_id int, city varchar(20), coffee " +
"decimal(6,2), merch decimal(6,2), " +
"total decimal(6,2))");
b1 = new BigDecimal("3450.55");
b2 = new BigDecimal("2005.21");
b3 = new BigDecimal("5455.76");
stmt.addBatch("insert into Coffee_Houses " +
"values(10023, Mendocino, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("4699.39");
b2 = new BigDecimal("3109.03");
b3 = new BigDecimal("7808.42");
stmt.addBatch("insert into Coffee_Houses " +
"values(33002, Seattle, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("5386.95");
b2 = new BigDecimal("2841.27");
b3 = new BigDecimal("8228.22");
stmt.addBatch("insert into Coffee_Houses " +
"values(100040, SF, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("3147.12");
b2 = new BigDecimal("3579.52");
b3 = new BigDecimal("6726.64");
stmt.addBatch("insert into Coffee_Houses " +
"values(32001,Portland, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("2863.35");
b2 = new BigDecimal("1874.62");
b3 = new BigDecimal("4710.97");
stmt.addBatch("insert into Coffee_Houses " +
"values(10042,SF, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("1987.77");
b2 = new BigDecimal("2341.21");
b3 = new BigDecimal("4328.98");
stmt.addBatch("insert into Coffee_Houses " +
"values(10024, Sacramento, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("2692.69");
b2 = new BigDecimal("1121.21");
b3 = new BigDecimal("8312.90");
stmt.addBatch("insert into Coffee_Houses " +
"values(10039,Carmel, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("1533.48");
b2 = new BigDecimal("1007.02");
73
74
FILTEREDROWSET
b3 = new BigDecimal("2450.50");
stmt.addBatch("insert into Coffee_Houses " +
"values(10041,LA, "+b1+", "+b2+", "+b3+")");
b1 = new BigDecimal("2733.83");
b2 = new BigDecimal("1550.48");
b3 = new BigDecimal("4284.31");
stmt.addBatch("insert into Coffee_Houses " +
"values(33002,Olympia, "+b1+", "+b2+", "+b3+")");
stmt.executeBatch();
con.commit();
// Now all the data has been inserted into the DB.
// Create a FilteredRowSet, set the properties and
// populate it with this data.
FilteredRowSet frs = new FilteredRowSetImpl();
frs.setUsername(strUserId);
frs.setPassword(strPassword);
frs.setUrl(strUrl);
frs.setCommand("select * from Coffee_Houses");
frs.execute(con);
con.close();
Range3 combinedFilter = new Range3(loArray,
hiArray, idxArray);
frs.setFilter(combinedFilter);
while(frs.next()) {
System.out.println("Store ID is: " + frs.getInt(1));
System.out.println("City is: " + frs.getString(2));
}
frs.close();
} catch(Exception e ) {
System.err.print("Caught unexpected Exception: ");
System.err.println(+ e.getMessage());
}
}
}
CODE SAMPLE 3
The following code denes Range3, a class implementing the interface Predicate. A
Range3 object, which uses two criteria, allows only stores in San Franciso, California. A Range3 object is used as the lter for FilteredRowSetSample3.
==============================================================
import javax.sql.rowset.*;
import com.sun.rowset.*;
import java.util.*;
import java.lang.*;
import java.sql.*;
import javax.sql.RowSet;
import java.io.Serializable;
public class Range3 implements Predicate {
private Object lo[];
private Object hi[];
private int idx[];
public Range3(Object[] lo, Object[] hi, int[] idx) {
this.lo = lo;
this.hi = hi;
this.idx = idx;
}
public boolean evaluate(RowSet rs) {
boolean bool1 = false;
boolean bool2 = false ;
try { CachedRowSet crs = (CachedRowSet)rs;
// Check the present row determine if it lies
// within the filtering criteria.
for (int i = 0; i < idx.length; i++) {
if ( ((rs.getObject(idx[i]).toString()).compareTo(lo[i].toString()) < 0) ||
((rs.getObject(idx[i]).toString()).compareTo(hi[i].toString()) > 0) ) {
bool2 = true; // outside filter constraints
} else {
bool1 = true; // within filter constraints
}
}
} catch( SQLException e) {
}
75
76
FILTEREDROWSET
if (bool2) {
return false;
} else {
return true;
}
}
// No implementation needed.
public boolean evaluate(Object value, String columnName) {
return false;
}
// No implementation needed.
public boolean evaluate(Object value, int columnIndex) {
return false;
}
}
6
WebRowSet
A
77
78
WEBROWSET
ment composed using the SAAJ API. In this chapter you will see how much easier it is to send the price data using a WebRowSet object, which can write itself as
an XML document with a single method call.
Although priceList has no data yet, it has the default properties of a BaseRowSet
object. Its SyncProvider object is at rst set to the RIOptimisticProvider implementation, which is the default for all disconnected RowSet objects. However, the
WebRowSet implementation resets the SyncProvider object to be the RIXMLProvider
implementation. You will learn more about the RIXMLProvider implementation in
the section Synchronizing Back to the Data Source [xref].
Getting back to our scenario, the Coffee Break headquarters regularly sends
price list updates to its Web site. The Java Web Services Tutorial stated that this
routine updating was done, but it did not show how it was done. This chapter on
WebRowSet objects will show one way you can send the latest price list in an
XML document.
The price list consists of the data in the columns COF_NAME and PRICE from the
table COFFEES. The following code fragment sets the properties needed and populates priceList with the price list data.
priceList.setCommand("SELECT COF_NAME, PRICE FROM COFFEES");
priceList.setURL("jdbc:mySubprotocol:myDatabase");
priceList.setUsername("myUsername");
priceList.setPassword("myPassword");
priceList.execute();
At this point, in addition to the default properties, priceList contains the data in the
columns COF_NAME and PRICE from the COFFEES table and also the metadata
about these two columns.
The following code writes the XML document representing priceList to the FileWriter object writer instead of to an OutputStream object. The FileWriter class is a convenience class for writing characters to a le.
java.io.FileWriter writer = new java.io.FileWriter("priceList.xml");
priceList.writeXml(writer);
The other two versions of the method writeXml let you populate a WebRowSet
object with the contents of a ResultSet object before writing it to a stream. In the
following line of code, the method writeXml reads the contents of the ResultSet
object rs into priceList and then writes priceList to the FileOutputStream object oStream
as an XML document.
priceList.writeXml(rs, oStream);
79
80
WEBROWSET
In the next line of code, writeXml populates priceList with the contents of rs, but it
writes the XML document to a FileWriter object instead of to an OutputStream
object.
priceList.writeXml(rs, writer);
Note that you can read the XML description into a new WebRowSet object or into
the same WebRowSet object that called the writeXml method. In our secenario,
where the price list information is being sent from headquarters to the web site,
you would use a new WebRowSet object, as shown in the following lines of code.
WebRowSet recipient = new WebRowSetImpl();
java.io.FileReader reader = new java.io.FileReader("priceList.xml");
recipient.readXml(reader);
The WebRowSet XML Schema, itself an XML document, denes what an XML
document representing a WebRowSet object will contain and also the format in
which it must be presented. Both the sender and the recipient use this schema
because it tells the sender how to write the XML and the recipient how to parse
the XML. Because the actual writing and reading is done internally by the implementations of the methods writeXml and readXml, you, as a user, do not need to
understand what is in the WebRowSet XML Schema document. For reference,
however, you can nd the schema at the end of this chapter. If you want to learn
more about XML, you can also refer to the XML chapter in the Java Web Services Tutorial.
Any XML document contains elements and subelements in a hierarchical structure. The following are the three main elements in an XML document describing
a WebRowSet object:
properties
metadata
data
Element tags signal the beginning and end of an element. For example, <properties> signals the beginning of the properties element, and </properties> signals its
end. <map/> is a shorthand way of saying that the map subelement (one of the subelements in the properties element) has not been assigned a value. The XML
shown in this chapter uses spacing and indentation to make it easier to read, but
they are not used in an actual XML document, where spacing does not mean
anything.
The next three sections show you what the three main elements contain for the
WebRowSet priceList, created earlier in this chapter. Lets assume that the data in
priceList corresponds to the data in Table 61.
Table 61 PRICE_LIST
COF_NAME
PRICE
Colombian
7.99
French_Roast
8.99
Espresso
9.99
Colombian_Decaf
8.99
81
82
WEBROWSET
Table 61 PRICE_LIST
COF_NAME
PRICE
French_Roast_Decaf
9.99
Properties
Calling the method writeXml on priceList would produce an XML document
describing priceList. The properties section of this XML document would look
like the following.
<properties>
<command>select COF_NAME, PRICE from COFFEES</command>
<concurrency>1</concurrency>
<datasource/>
<escape-processing>true</escape-processing>
<fetch-direction>0</fetch-direction>
<fetch-size>0</fetch-size>
<isolation-level>1</isolation-level>
<key-columns/>
<map/>
<max-field-size>0</max-field-size>
<max-rows>0</max-rows>
<query-timeout>0</query-timeout>
<read-only>false</read-only>
<rowset-typ>TRANSACTION_READ_UNCOMMITTED</rowset-type>
<show-deleted>false</show-deleted>
<table-name/>
<url>jdbc:thin:oracle</url>
<sync-provider>
<sync-provider-name>com.rowset.provider.RIOptimisticProvider
</sync-provider-name>
<sync-provider-vendor>Sun Microsystems</sync-provider-vendor>
<sync-provider-version>1.0</sync-provider-version>
<sync-provider-grade>LOW</sync-provider-grade>
<data-source-lock>NONE</data-source-lock>
<sync-provider/>
</properties>
You will notice that some properties have no value. For example, the datasource
property is indicated with <datasource/>, which is a shorthand way of saying <datasource></datasource>. No value is given because the url property is set. Any connections that are established will be done using this JDBC URL, so no DataSource
METADATA
object needs to be set. Also, the username and password properties are not listed
because they need to remain secret.
Metadata
The metadata section of the XML document describing a WebRowSet object contains information about the columns in that WebRowSet object. The following
shows what this section looks like for the WebRowSet object priceList. Because
priceList has two columns, the XML document describing it has two <column-definition> elements. Each <column-definition> element has subelements giving information about the column being described.
<metadata>
<column-count>2</column-count>
<column-definition>
<column-index>1</column-index>
<auto-increment>false&</auto-increment>
<case-sensitive>true</case-sensitive>
<currency>false</currency>
<nullable>1</nullable>
<signed>false</signed>
<searchable>true</searchable>
<column-display-size>10</column-display-size>
<column-label>COF_NAME</column-label>
<column-name>COF_NAME</column-name>
<schema-name/>
<column-precision>10</column-precision>
<column-scale>0</column-scale>
<table-name/>
<catalog-name/>
<column-type>1</column-type>
<column-type-name>VARCHAR</column-type-name>
</column-definition>
<column-definition>
<column-index>2</column-index>
<auto-increment>false</auto-increment>
<case-sensitive>true</case-sensitive>
<currency>true</currency>
<nullable>1</nullable>
<signed>false</signed>
<searchable>true</searchable>
<column-display-size>10</column-display-size>
<column-label>PRICE</column-label>
83
84
WEBROWSET
<column-name>PRICE</column-name>
<schema-name/>
<column-precision>10</column-precision>
<column-scale>2</column-scale>
<table-name/>
<catalog-name/>
<column-type>3</column-type>
<column-type-name>DECIMAL</column-type-name>
</column-definition>
</metadata>
From this metadata section, you can see that there are two columns in each row.
The rst column is COF_NAME, which holds values of type VARCHAR. The second column is PRICE, which holds values of type DECIMAL, and so on. Note that
the column types in the schema are the data types used in the database, not types
in the Java programming language (Java types). To get, set, or update values,
though, you use getter, setter, and updater methods that use a Java type. For
example, to set the value in the column COF_NAME, you use the method setString,
and the driver converts the value to VARCHAR before sending it to the database.
Data
The data section gives the values for each column in each row of a WebRowSet
object. If you have populated priceList and not made any changes to it, the data
element of the XML document will look like the following. In the next section
you will see how the XML document changes when you modify the data in
priceList.
For each row there is a <currentRow> element, and because priceList has two columns, each <currentRow> element contains two <columnValue> elements.
<data>
<currentRow>
<columnValue>
Colombian
</columnValue>
DATA
<columnValue>
7.99
</columnValue>
</currentRow>
<currentRow>
<columnValue>
French_Roast
</columnValue>
<columnValue>
8.99
</columnValue>
</currentRow>
<currentRow>
<columnValue>
Espresso
</columnValue>
<columnValue>
9.99
</columnValue>
</currentRow>
<currentRow>
<columnValue>
Colombian_Decaf
</columnValue>
<columnValue>
8.99
</columnValue>
</currentRow>
<currentRow>
<columnValue>
French_Roast_Decaf
</columnValue>
<columnValue>
9.99
</columnValue>
</currentRow>
</data>
85
86
WEBROWSET
Inserting a Row
If the owner of the Coffee Break chain wants to add a new coffee to his price list,
the code might look like this.
priceList.moveToInsertRow();
priceList.updateString("COF_NAME", "Kona");
priceList.updateBigDecimal("PRICE", new BigDecimal("8.99");
priceList.insertRow();
priceList.moveToCurrentRow();
priceList.acceptChanges();
To reect the insertion of the new row, the XML document will have the following <insertRow> element added to it.
<insertRow>
<columnValue>
Kona
</columnValue>
<columnValue>
8.99
</columnValue>
</insertRow>
DELETING A ROW
Deleting a Row
The owner decides that Espresso is not selling enough and should be dropped
from the coffees sold at the Coffee Break. He therefore wants to delete Espresso
from the price list. Espresso is in the third row of priceList, so the following lines
of code delete it.
priceList.absolute(3);
priceList.deleteRow();
The following <deleteRow> element will appear after the second row in the data
section of the XML document, indicating that the third row has been deleted.
<deleteRow>
<columnValue>
Espresso
</columnValue>
<columnValue>
9.99
</columnValue>
</deleteRow>
Modifying a Row
The owner further decides that the price of Colombian coffee is too expensive
and wants to lower it to 6.99 a pound. The following code sets the new price for
Colombian coffee, which is in the rst row, to 6.99 a pound.
priceList.first();
priceList.updateBigDecimal("PRICE", new BigDecimal("6.99"));
The XML document will reect this change in a <modifyRow> element that gives
both the old value (in the <columnValue> element) and the new value (in the <updateValue> element), as shown here. The value for the rst column did not change, so
there is an <updateValue> element for only the second column.
<modifyRow>
<columnValue>
Colombian
</columnValue>
<columnValue>
7.99
</columnValue>
87
88
WEBROWSET
<updateValue>
6.99
</updateValue>
</modifyRow>
At this point, with the insertion of a row, the deletion of a row, and the modication of a row, the XML document for priceList would look like the following.
--->ll in what the data section reecting these changes would look like
try {
Class.forName(className);
} catch (java.lang.ClassNotFoundException e) {
System.err.print("ClassNotFoundException: ");
System.err.println(e.getMessage());
}
try {
Connection con = DriverManager.getConnection(
strUrl, strUserId, strPassword);
con.setAutoCommit(false);
Statement stmt = con.createStatement();
stmt.addBatch("drop table priceList");
stmt.addBatch("create table priceList(" +
"cof_name varchar(30), price decimal(6,2))");
b = new BigDecimal("7.99");
stmt.addBatch("insert into priceList values(" +
" Colombian , "+b+")");
b = new BigDecimal("8.99");
stmt.addBatch("insert into priceList values(" +
" French_Roast , "+b+")");
b = new BigDecimal("9.99");
stmt.addBatch("insert into priceList values(" +
" Espresso , "+b+")");
b = new BigDecimal("8.99");
stmt.addBatch("insert into priceList values(" +
" Colombian_Decaf , "+b+")");
b = new BigDecimal("9.99");
stmt.addBatch("insert into priceList values(" +
" French_Roast_Decaf , "+b+")");
stmt.executeBatch();
con.commit();
con.close();
// Create a WebRowSet and set its properties.
WebRowSet sender = new WebRowSetImpl();
sender.setUrl(strUrl);
sender.setUsername(strUserId);
sender.setPassword(strPassword);
sender.setCommand("select * from priceList");
sender.setKeyColumns(keyCols);
89
90
WEBROWSET
=========================================================
91
92
WEBROWSET
<xs:element name= concurrency type= xs:string />
<xs:element name= datasource type= xs:string />
<xs:element name= escape-processing type= xs:string />
<xs:element name= fetch-direction type= xs:string />
<xs:element name= fetch-size type= xs:string />
<xs:element name= isolation-level type= xs:string />
<xs:element name= key-columns >
<xs:complexType>
<xs:sequence minOccurs= 0 maxOccurs= unbounded >
<xs:element name= column type= xs:string />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name= map >
<xs:complexType>
<xs:sequence minOccurs= 0 maxOccurs= unbounded >
<xs:element name= type type= xs:string />
<xs:element name= class type= xs:string />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name= max-field-size type= xs:string />
<xs:element name= max-rows type= xs:string />
<xs:element name= query-timeout type= xs:string />
<xs:element name= read-only type= xs:string />
<xs:element name= rowset-type type= xs:string />
<xs:element name= show-deleted type= xs:string />
<xs:element name= table-name type= xs:string />
<xs:element name= url type= xs:string />
<xs:element name= sync-provider >
<xs:complexType>
<xs:sequence>
<xs:element name= sync-provider-name type= xs:string />
<xs:element name= sync-provider-vendor type= xs:string />
<xs:element name= sync-provider-version type= xs:string />
<xs:element name= sync-provider-grade type= xs:string />
<xs:element name= data-source-lock type= xs:string />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name= metadata >
<xs:complexType>
<xs:sequence>
<xs:element name= column-count type= xs:string />
<xs:choice>
93
94
WEBROWSET
<xs:complexType>
<xs:sequence minOccurs= 0 maxOccurs= unbounded >
<xs:element ref= wrs:columnValue />
<xs:element ref= wrs:updateValue />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name= modifyRow minOccurs= 0 maxOccurs= unbounded >
<xs:complexType>
<xs:sequence minOccurs= 0 maxOccurs= unbounded >
<xs:element ref= wrs:columnValue />
<xs:element ref= wrs:updateValue />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>