Sun Java Solaris Communities My SDN Account Join SDN
 
Article

The JDBC 2.0 Optional Package

 


By Maydene Fisher

The JDBC 2.0 Optional Package (formerly Standard Extension API) adds significant functionality for using databases and other data sources. Some of this functionality applies mainly to enterprise computing, and some of it applies more generally. With the new Optional Package, JDBC technology offers universal data access from the Java 2 platform and enters the world of heavy duty, server-centric database applications.

First, let's get some perspective on how the Standard Extension API fits into the JDBC 2.0 API. The JDBC 2.0 API includes two packages:

  • The java.sql package, which is the JDBC 2.0 core API. This includes the original JDBC API, referred to as the JDBC 1.0 API, plus the new core API that has been added. This package is included in the download of the Java 2 Platform SDK, Standard Edition.

  • The javax.sql package, which is the JDBC 2.0 Standard Extension API. This package is entirely new and is available as a separate download or as part of the Java 2 Platform SDK, Enterprise Edition.

This article summarizs the new functionality in the javax.sql package and gives you some examples of how to use it. This new API falls into the following general categories:

  • The DataSource interface for working with the Java Naming and Directory Interface (JNDI)--a better way for making a connection to a data source.

  • Connection pooling: a means of reusing connections rather than having a new one created for every connection.

  • Distributed transactions: a means of including more than one database server in a transaction.

  • Rowsets: JavaBeans components that contain a set of rows, used mainly for passing data to a thin client or providing scrollability to a result set.

A Better Way to Connect to a Data Source

The DataSource interface provides an alternative to the DriverManager class for making a connection to a data source. Using a DataSource implementation is better for two important reasons: It makes code more portable, and it makes code easier to maintain.

A DataSource object represents a real world data source. Depending on how it is implemented, the data source can be anything from a relational database to a spreadsheet or a file in tabular format. When a DataSource object has been registered with a JNDI naming service, an application can retrieve it from the naming service and use it to make a connection to the data source it represents.

Information about the data source and how to locate it, such as its name, the server on which it resides, its port number, and so on, is stored in the form of properties on the DataSource object. This makes an application more portable because it does not need to hard code a driver name, which often includes the name of a particular vendor, the way an application using the DriverManager class does. It also makes maintaining the code easier because if, for example, the data source is moved to a different server, all that needs to be done is to update the relevant property. None of the code using that data source needs to be touched.

A systems administrator or someone working in that capacity deploys a DataSource object. Deployment, which involves setting the DataSource object's properties and then registering it with a JNDI naming service, is generally done with a tool. As part of the registration process, the systems administrator will associate the DataSource object with a logical name. This name can be almost anything, usually being a name that describes the data source and that is easy to remember. In the example that follows, the logical name for the data source is InventoryDB . By convention, logical names for DataSource objects are in the subcontext jdbc , so the full logical name in this example is jdbc/InventoryDB .

Once a DataSource object has been deployed, application programmers can use it to make a connection to the data source it represents. The following code fragment shows how to retrieve the DataSource object associated with the logical name jdbc/InventoryDB and then uses it to get a connection. The first two lines use JNDI API to get the DataSource object; the third line uses JDBC API to get the connection.

  Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/InventoryDB");
Connection con = ds.getConnection("myUserName", "myPassword");

In a basic DataSource implementation, the Connection object that is returned by the DataSource.getConnection method is identical to a Connection object returned by the DriverManager.getConnection method. Because of the advantages it offers, using a DataSource object is the recommended way to obtain a connection. It is expected that most JDBC 2.0 technology-based drivers will include a basic implementation of the DataSource interface as well as the javax.sql package, so it should be readily available.

For the general application programmer, using a DataSource object is a matter of choice. Programmers writing JDBC applications that include connection pooling and/or distributed transactions, however, must use a DataSource object to get their connections. The reasons for this are explained in the following sections.

Connection Pooling

Connection pooling is a mechanism whereby when an application closes a connection, that connection is recycled rather than being destroyed. Because establishing a connection is an expensive operation, reusing connections can improve performance dramatically by cutting down on the number of new connections that need to be created.

Assuming that an application wants to connect to a data source represented by the DataSource object whose logical name is EmployeeDB , the code for getting a connection that uses connection pooling looks like this:

  Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/EmployeeDB");
Connection con = ds.getConnection("myUserName", "myPassword");

Note that the code is identical to that in the previous example except for the logical name, which is different because the application wants to connect to a different data source. Whether or not the connection returned by a call to the DataSource.getConnection method will be a pooled connection depends entirely on how the DataSource class being used has been implemented. If it has been implemented to work with a middle tier server that supports connection pooling, a DataSource object will automatically return Connection objects that will be pooled and reused.

Just as there is no change in code to get a pooled connection, there is virtually no difference in the code for using a pooled connection. The only change is that the connection should be closed in a finally block, which is not a bad idea for closing any type of connection. That way, even if a method throws an exception, the connection will be closed and put back into the connection pool. The code for the finally block, which comes after the appropriate try and catch blocks, should look like this:

  } finally { 
if (con != null) con.close():
}

This finally block ensures that a valid connection will be recycled.

Distributed Transactions

The situation with obtaining a connection that can be used for distributed transactions is similar to that for getting a pooled connection. Again, the difference is in the way the DataSource class is implemented, not in the application code for obtaining the connection. Assuming that the DataSource class has been implemented to work with a distributed transaction infrastructure in the middle tier, the following code fragment will get a connection that may participate in distributed transactions:

  Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/EmployeeDB");
Connection  con = ds.getConnection("myUserName", "myPassword");

For performance reasons, a DataSource object that has been implemented to produce connections for distributed transactions is almost always implemented to produce connections that are pooled as well.

From the application programmer's point of view, there is practically no difference between a regular connection and a connection that can be used for for distributed transactions. The only difference is that the transaction's boundaries, that is, when it begins and when it ends, are handled by a transaction manager behind the scenes. This means that the application should not do anything that could interfere with what the transaction manager is doing. So the application code cannot call the commit or rollback methods directly, and it cannot enable auto-commit mode, which calls the commit or rollback methods automatically when a statement is completed.

Specifically, the following lines of code, in which con is a Connection object that can be used for distributed transactions, show what you should not do while con is participating in a distributed transaction:

con.commit();

or

con.rollback();

or

con.setAutoCommit(true);

The default for a regular connection is to have its auto-commit mode turned on. A Connection object that can be used for distributed transactions, however, has its auto-commit mode turned off by default. Note that a connection can be used for nondistributed transactions even though it is capable of being used for distributed transactions, and the restrictions regarding transaction boundaries apply only while a connection is part of a distributed transaction.

Deployment of a DataSource object that supports connection pooling involves the deployment of a ConnectionPoolDataSource object that is used by a connection pooling module in the middle tier of a three-tier architecture. Similarly, deployment of a DataSource object that supports distributed transactions involves the deployment of an XADataSource object that is used by a distributed transaction infrastructure in the middle tier. These ConnectionPoolDataSource and XADataSource objects are completely transparent to the application programmer and are provided by driver vendors. As with the deployment of a basic DataSource implementation, a systems administrator or someone working in that capacity takes care of deploying ConnectionPoolDataSource objects and XADataSource objects. More specific information can be found in the book JDBCtm API Tutorial and Reference, Second Edition , by White, Fisher, Cattell, Hamilton, and Hapner, published by Addison Wesley as part of the Java Series.

Rowsets

A RowSet object is a container for a set of rows. It can be implemented in many ways, depending on its intended purpose. The RowSet interface and its related interfaces differ from the other parts of the JDBC 2.0 Standard Extension API in that they are not part of a driver implementation. A RowSet object is implemented on top of a driver and can be implemented by anyone.

Any type of rowset implements the RowSet interface, which extends the ResultSet interface. Thus, a RowSet object has all of the functionality of a ResultSet object: It can retrieve values with getXXX methods, update values programmatically with updateXXX methods, move its cursor with various cursor movement methods, and perform other relevant tasks.

But, of course, we are most interested in the new functionality provided by the RowSet interface. Being a JavaBeans component, a RowSet object has methods for adding and removing listeners and for getting and setting its properties. One of these properties is a command string, which is always a query, and the RowSet interface provides methods for setting this command's parameters and for executing it. This means that a RowSet object can execute its command string and populate itself with the contents of the result set that is produced. Alternatively, a RowSet object can be implemented to populate itself with data from any tabular data source, so it is not limited to just relational databases.

After getting data from a data source, a RowSet object can disconnect itself from that data source, which makes it very lean if the data is not overly large. A rowset can also be serialized, so being small and serializable, a disconnected rowset is ideal for being passed over a network to a thin client, such as a PDA or other small device.

A rowset can be updated and then reconnected to its data source in order to propagate the updated values back to the data source. If listeners have been set up, they will be notified every time the rowset's cursor moves or its content changes. For example, a graphical user interface component (GUI), such as a bar graph, coud be registered as a listener. It would be notified when an event occurred on the rowset and could be implemented, for example, so that it redraws itself to reflect any changes in the rowset's data.

The RowSet interface can be implemented in any number of ways, depending on what you want it to do. To make implementing rowsets easier, Sun Microsystems is developing standard implementations for the five most likely uses of a RowSet object as Java Specification Request (JSR) 114 under the Java Community Process. You can download the latest version of the specification from:

http://java.sun.com/products/jdbc/download.html

For example, the CachedRowSet implementation provides a set of rows that can be disconnected from the data source, serialized, and sent over a network to thin clients.

In addition to making it easy to send data over a network, another common use for a rowset is to add scrollability and/or updatability to a ResultSet object that does not otherwise have that functionality. By simply creating a RowSet object and populating it with a ResultSet object's data, a programmer can operate on the scrollable and/or updatable RowSet object instead.

The JdbcRowSet class, unlike the CachedRowSet class, is an example of a rowset that always maintains a connection to its data source while it is in use. Accordingly, it can be used to provide a thin layer around a JDBC ResultSet object at run time and thereby make a JDBC technoloby-based driver look like a JavaBeans component. If this is done, the driver, presented as a JdbcRowSet object, can be one of the components a visual development tool makes available for assembling an application.

Another variation on a rowset is an implementation that is geared to working in a distributed client/server application using HTTP/XML (Hypertext Transfer Protocol/Extensible Markup Language) to communicate with the middle tier, where the application server resides. In such an implementation, web clients can talk to Java servlets that provide data access. This implementation is similar to the CachedRowSet implementation in that both are a good means of sending data to a thin client, but differs in that the CachedRowSet class uses RMI/IIOP (Remote Method Invocation/Internet Interoperability Protocol) as the protocol it uses to talk with the middle tier rather than HTTP/XML.

For an online tutorial on rowsets that features an example that uses a CachedRowSet object, visit the Java Developer Connection, located at the following URL:

http://developer.java.sun.com/developer/Books/JDBCTutorial/chapter5.html

This tutorial gives you basic information about different types of rowsets and what they can do.

Summary

The JDBC 2.0 Standard Extension API moves JDBC technology into a whole new space. By allowing a connection to be made using a DataSource object registered with a JNDI naming service, it makes application code more portable and easier to maintain. The new API also supports pooled connections and connections that can be used in distributed transactions. Finally, it makes it possible for a Java application to do things like pass a set of tabular data over a network or effectively make a nonscrollable result set scrollable. These are all accomplished with little effort from the application programmer because the bulk of the work is done with tools or behind the scenes.