To access a database management system (DBMS) in Java, you need a JDBC (Java Database Connectivity) driver. You may write such drivers, which range from types 1 to 4, in pure Java or a combination of Java and Java Native Interface (JNI) methods. The industry trend is towards the more robust types 3 and 4 pure-Java drivers. Type 3 drivers shine when supporting Internet deployment in environments that connect to a variety of DBMS servers requiring numerous concurrently connected users where performance and scalability are major concerns. Therefore, to develop a high-performance, Internet-deployable application, you'll often find it useful to convert your existing type 1 or 2 drivers to type 3 drivers.

Read the whole series

In this three-part series, we first introduce our own type 3 JDBC driver's architecture and design (Part 1), then show how to implement and deploy the driver (Part 2), and finish by explaining how you can add advanced features to the driver, like SQL logging or connection pooling (Part 3).

Note: Before you read this article, you may wish to read Nitin Nanda's "JDBC Drivers in the Wild" (JavaWorld, July 2000) to better understand JDBC drivers.

The JDBC driver architecture

JDBC provides a programming-level interface for uniformly communicating with databases. To use the JDBC API with a particular DBMS, you need a JDBC driver to mediate between JDBC technology and the database. JDBC drivers divide into four types or levels. Each type defines a JDBC driver implementation with increasingly higher levels of platform independence, performance, and deployment administration. The four types are:

  • Type 1: JDBC-ODBC (Open Database Connectivity) Bridge
  • Type 2: Native-API, partly Java driver
  • Type 3: Network-protocol, all-Java driver
  • Type 4: Native-protocol, all-Java driver

All JDBC drivers implement the four important JDBC classes: Driver, Connection, Statement, and ResultSet. The DriverManager class included with the java.sql package tracks the loaded JDBC drivers. The client application retrieves the desired database connections through the DriverManager class. The JDBC Driver class loads whenever a call comes to the driver:

Class.forName("com.jw.client.JWDriver");
                                       
                                    

The specified JDBC driver's static code block runs during the JDBC driver class's loading, which registers the driver with the DriverManager. Now, whenever a client program retrieves a database connection with the DriverManager.getConnection() method, the DriverManager in turn calls the Driver.connect() method. Every JDBC driver must implement the java.sql.Driver interface. So, the JDBC driver's connect() method checks whether the driver URL is correct, then returns the Connection within its connect() method.

Sample type 3 driver's architecture

To show you a type 3 driver's inner workings, we've constructed our own type 3 JDBC driver for this series. As Figure 1 shows, our JDBC type 3 driver—the network-protocol/all-Java driver—follows a three-tiered approach, whereby the JDBC database requests pass through the network to the middle-tier server. The middle-tier server then translates the requests (directly or indirectly) to the database-specific native-connectivity interface to further the request to the database server. The middle-tier server, written in Java, accesses the database server with a type 1 JDBC-ODBC Bridge driver.

Figure 1. JDBC type 3 driver architecture. Click on thumbnail to view full-size image.

For applets, the client-tier driver files reside in the middle-tier server and download along with the applet. In the JDBC driver, RMI (Remote Method Invocation) serves as the net protocol for communicating between the driver's client and server tiers. The JDBC-ODBC Bridge lets the driver middle tier translate the JDBC requests to the database server.

The driver's client tier, which provides the standard JDBC interface to the client programs, consists of a Driver class that implements the java.sql.Driver interface. It also consists of the implementations of JDBC's Connection, Statement and ResultSet interfaces.

Client programs, applets for example, are developed using the type 3 driver's client-tier class. Since these Driver client classes implement the JDBC interface, client programs receive the standard JDBC functionality. The Driver client-tier classes internally maintain the references of the corresponding remote interfaces exposed by the middle tier. Such remote interfaces include basic methods the client-tier classes use to process the JDBC requests from the programs. The application program calls JDBC methods in the client-tier objects implemented by the Driver. Those calls then become delegated to the middle tier using remote interface methods via RMI. Therefore, the Driver client classes manage the internal RMI communications with the middle tier.

The driver's server tier, an RMI server, uses the JDBC-ODBC Bridge—a type 1 driver—to finally communicate with the database. The driver's server tier includes the four remote interfaces and their implementations. The remote interfaces provide interface to the JDBC Driver, Connection, Statement, and ResultSet, respectively. The classes that implement the remote interfaces internally maintain the JDBC-ODBC Bridge driver's Connection, Statement, and ResultSet objects. When the client-tier class forwards a call to a remote interface, that interface's remote implementation uses the contained JDBC object to interact with the database.

Now that you understand our type 3 JDBC driver's architecture, let's examine its client- and server-tier classes.

JDBC driver class diagrams

To implement a type 3 JDBC driver, you must create both the driver's client and middle tiers. The client tier's classes reside in the com.jw.client package, while the middle tier's classes reside in the com.jw.server package.

Let's first examine the client tier.

Client-tier classes

The client-tier package, com.jw.client, features the following classes:

  • com.jw.client.JWDriver: The JDBC Driver implementation class
  • com.jw.client.JWConnection: The JDBC Connection implementation class
  • com.jw.client.JWStatement: The JDBC Statement implementation class
  • com.jw.client.JWResultSet: The JDBC ResultSet implementation class

Figure 2 shows the class diagram depicting the relationship between the Driver and Connection classes on the client and middle tiers.

Figure 2. The Driver and Connection class diagrams. Click on thumbnail to view full-size image.

Let's examine the JWDriver class in more detail.

The JWDriver class

The com.jw.client.JWDriver class implements the java.sql.Driver interface, which provides methods to register itself with the DriverManager and create new database connections. The class acts as a wrapper over the remote Driver to provide the JDBC driver interface. The JWDriver class loads whenever it's called in the program:

Class.forName("com.jw.client.JWDriver");
                                       
                                    

In the code above, the forName() method call invokes the JWDriver static clause, which registers itself with the DriverManager. Here is the code for the static clause:

static
                                       {
                                       try
                                       {
                                       // Register the JWDriver with DriverManager
                                       JWDriver driverInst = new JWDriver();
                                       DriverManager.registerDriver(driverInst);
                                       System.setSecurityManager(new RMISecurityManager());
                                       }
                                       ...
                                       }
                                       
                                    

The JWDriver class also maintains a reference to the remote driver, com.jw.server.IRemoteDriver, located on the middle-tier server. The remote driver reference creates the database connections for the client driver JWDriver class. So, when the calling program needs a database connection, it calls the DriverManager.getConnection() method. The DriverManager's JWDriver.connect() method gets invoked, which in turn uses the remote driver's reference to get the database connection.

In short, the JWDriver.connect() method:

  • Compares the driver's URL with the URL the client program passes and returns null if it is the wrong kind of driver to connect to.
  • If no remote driver reference exists, it creates such a reference using the Naming.lookup() method. JWDriver.connect() maintains the remote driver reference to create the database connections remotely:

    if(remoteDriver == null)
                                           {
                                           remoteDriver= RemoteDriver)Naming.lookup("rmi://"+serverName
                                           +":1099"+"/RemoteDriver");
                                           }
                                           
                                        
    
  • Creates, using the remote driver created above, a database connection and returns it to the calling program. The function call remoteDriver.getConnection() is a remote call to the JDBC driver's middle tier. The remote Connection retrieved from the middle tier is stored as a reference in the JWConnection class object. The JWConnection class's object then returns to the calling program:

    {
                                           IRemoteConnection remoteConInstance = 
                                           (IRemoteConnection)remoteDriver.getConnection();
                                           localConInstance = new JWConnection(remoteConInstance);
                                           ...
                                           return (Connection)localConInstance;
                                           }
                                           
                                           
                                        
    

Next, let's examine the JWConnection class.

JWConnection class

The com.jw.client.JWConnection class implements the JDBC Connection interface and contains the reference to the remote server IRemoteConnection interface.

The JWDriver.connect() method, which returns the JWConnection object reference to the client program, creates the JWConnection object. The client then calls any JDBC Connection interface method on the returned JWConnection object reference. The JWConnection object internally delegates the call to the remote server Connection for further action. For example, when the client calls conn.createStatement() (where conn is a reference of the JWConnection object), it internally calls RemoteConnection.createStatement(), which returns a remote Statement reference. The JWConnection's createStatement() method then creates a JWStatement object. Finally, a JWStatement object reference returns to the client program, which contains the remote Statement reference:

public Statement createStatement()  throws SQLException
                                       {
                                       try
                                       {
                                       IRemoteStatement remStmt = 
                                       (IRemoteStatement) remoteConnection.createStatement();
                                       JWStatement localStmtInstance = new JWStatement(remStmt);
                                       return (Statement)localStmtInstance;
                                       }      
                                       }
                                       
                                    

Figure 3 shows the class diagrams depicting the relationship between the Connection and Statement classes on the client and middle tiers.

Figure 3. Class diagrams for Connection and Statement classes. Click on thumbnail to view full-size image.

Next, we examine the JWStatement class in more detail.

The JWStatement class

The com.jw.client.JWStatement class, which implements the JDBC Statement interface, references the remote server Statement interface. The class acts as a wrapper on the remote Statement stub to provide the JDBC Statement interface. The JWConnection.createStatement() method, which returns the JWStatement object reference to the client program, creates JWStatement. The client then calls any method provided by the JDBC Statement interface on the returned JWStatement object reference. The JWStatement object delegates the call to the remote server Statement for processing. For example, when the client calls stmt.executeQuery() (where stmt is a reference of the JWStatement object), the executeQuery() method internally calls RemoteStatement.executeQuery(), which returns a remote ResultSet reference. Finally, a JWResultSet object reference returns to the client program, which contains the remote ResultSet reference:

Page 2 of 3
public
                                       ResultSet executeQuery(String sqlQuery)
                                       throws SQLException
                                       {
                                       try
                                       {
                                       IRemoteResultSet remoteRsInstance =
                                       (IRemoteResultSet) remoteStmt.executeQuery(sqlQuery);
                                       JWResultSet localRsInstance = new
                                       JWResultSet(remoteRsInstance);
                                       return (ResultSet)localRsInstance;
                                       }
                                       catch(RemoteException ex)
                                       {
                                       throw(new SQLException(ex.getMessage()));
                                       }
                                       }
                                       
                                    

Figure 4 shows the class diagram depicting the relationship between the Statement and ResultSet classes on the client and middle tiers.

Figure 4. The class diagram for the Statement and ResultSet classes. Click on thumbnail to view full-size image.

Next, we see JWResultSet.

The JWResultSet class

The com.jw.client.JWResultSet class implements the JDBC ResultSet interface. It references the remote server ResultSet interface and acts as a wrapper over the remote ResultSet stub for providing the JDBC ResultSet interface. The JWStatement.executeQuery() method, which returns the JWResultSet object reference to the client program, creates JWResultSet. The client then calls any method provided by the JDBC ResultSet interface on the returned JWResultSet object reference. The JWResultSet object internally forwards the request to the remote server ResultSet for processing. For example, when the client calls rs.next() (where rs is a reference of JWResultSet object), the next() method internally calls RemoteResultSet.getNextRow(), which returns an array of the row data. That row data is stored in the JWResultSet as the current row. When the client calls the rs.getString(1) method, the data is picked from the row data and returned:

public class JWResultSet implements java.sql.ResultSet
                                       {
                                       // The current ResultSet data row 
                                       private   Object[]   row;
                                       ...
                                       public boolean next() throws SQLException
                                       {
                                       try
                                       {
                                       // Get the current data row from remote ResultSet
                                       // All the getXXX methods will get data from local 'row'
                                       
                                       row = remoteResultSet.getNextRow();
                                       }
                                       catch(Exception ex)
                                       {
                                       return false;      
                                       }   
                                       
                                       if(row == null)
                                       {
                                       return false;
                                       }
                                       
                                       return true;
                                       }   
                                       }
                                       
                                    

The middle-tier classes

The middle-tier package, com.jw.server, includes the following classes:

  • com.jw.server.RemoteDriverImpl: The Driver class that retrieves the database connection and also acts as the RMI server
  • com.jw.server.RemoteConnectionImpl: The Connection class that retrieves the JDBC Statement
  • com.jw.server.RemoteStatementImpl: The Statement class that retrieves the ResultSet
  • com.jw.server.RemoteResultSetImpl: The ResultSet class

Let's examine each in more detail.

RemoteDriverImpl

RemoteDriverImpl acts as the RMI server (by extending UnicastRemoteObject) and provides the getConnection() method to the JDBC driver's client tier. (Figure 2 shows RemoteDriverImpl's other class and interface relationships.) RemoteDriverImpl implements com.jw.server.IRemoteDriver, an interface that extends Remote and provides the getConnection() method. The getConnection() method creates a JDBC Connection and returns it as an IRemoteConnection reference.

Since RemoteDriverImpl is the JDBC driver server's RMI server, it reads the ODBC data source name (DSN), username, and password from DriverSettings.properties files deployed in the server tier. These values create a JDBC Connection. Moreover, RemoteDriverImpl registers itself with the Naming service as "RemoteDriver," so that the driver's JDBC client layer can connect to the server using the Naming.lookup() method. Then it loads the JDBC-ODBC Bridge to create the database connections:

public static void main(String args[])
                                       {
                                       System.setSecurityManager(new RMISecurityManager());
                                       try
                                       {
                                       // Get the data source name, data source user, data source
                                       // Password and log level 
                                       ResourceBundle settingsBundle = ResourceBundle.getBundle(
                                       "DriverSettings");
                                       DSN = settingsBundle.getString("DSN");
                                       dsUser = settingsBundle.getString("User");
                                       dsPassword = settingsBundle.getString("Password");
                                       
                                       // Create a RemoteDriverImpl instance to register with naming service
                                       RemoteDriverImpl serverInstance = new RemoteDriverImpl();
                                       Naming.rebind("RemoteDriver",serverInstance);         
                                       
                                       // Load the JDBC-ODBC Bridge driver
                                       Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                                       }
                                       ...
                                       }
                                       
                                    

The getConnection() method creates a JDBC-ODBC connection, then returns a reference to the remote interface of the RemoteConnectionImpl object holding the JDBC-ODBC connection:

   
                                       public IRemoteConnection getConnection() 
                                       throws RemoteException,SQLException
                                       {
                                       String URL="jdbc:odbc:"+DSN;
                                       Connection sqlCon = 
                                       DriverManager.getConnection(URL,dsUser,dsPassword);
                                       RemoteConnectionImpl ConnectionInstance = 
                                       new RemoteConnectionImpl(sqlCon);
                                       return (IRemoteConnection)ConnectionInstance;
                                       }
                                       
                                    

Now let's see RemoteConnectionImpl.

RemoteConnectionImpl

The RemoteConnectionImpl class extends the UnicastRemoteObject class and implements the IRemoteConnection interface. (Figures 2 and 3 show RemoteConnectionImpl's class and interface relationships.) The IRemoteConnection interface provides methods to create JDBC statements and close the database connection. The RemoteConnectionImpl class also encapsulates the JDBC Connection object passed to it in its constructor.

The createStatement() method creates the JDBC-ODBC statement and returns the RemoteStatementImpl object's reference that holds the JDBC-ODBC statement:

public IRemoteStatement createStatement() throws RemoteException,SQLException
                                                                              {
                                                                                 RemoteStatementImpl StmtImplInstance =  new 
                                                                              RemoteStatementImpl(sqlConnection.createStatement());
                                                                                 return  (IRemoteStatement)StmtImplInstance;
                                                                              }
                                                                           
                                    

Next, the closeConnection() method simply closes the JDBC Connection object it encapsulates by calling the close() method:

public void closeConnection() throws RemoteException,SQLException
                                                                              {
                                                                                 sqlConnection.close();   
                                                                              }   
                                                                           
                                    

Let's now examine class RemoteStatementImpl.

RemoteStatementImpl

The RemoteStatementImpl class—shown in Figures 3 and 4—extends the UnicastRemoteObject class and implements the IRemoteStatement interface. The IRemoteStatement interface provides methods to create the JDBC ResultSet and close the statement object. The RemoteStatementImpl class also encapsulates the JDBC Statement object passed to it in its constructor.

The executeQuery() method creates a JDBC-ODBC ResultSet, then returns a reference to the remote interface of the RemoteResultSetImpl object holding the JDBC-ODBC ResultSet:

public
                                                                              IRemoteResultSet executeQuery(String Query) throws RemoteException,SQLException
                                                                              {
                                                                                 ResultSet rs = sqlStatment.executeQuery(Query);
                                                                                 RemoteResultSetImpl remoteRs = new RemoteResultSetImpl(rs);
                                                                                 return (IRemoteResultSet)remoteRs;
                                                                              }
                                                                           
                                    

The close() method simply closes the JDBC Statement object:

public
                                       void
                                                                              close() throws RemoteException, SQLException
                                                                              {
                                                                                 sqlStatment.close();
                                                                              }
                                                                           
                                    

Lastly, here is class RemoteResultSetImp.

RemoteResultSetImpl

The RemoteResultSetImpl class extends the UnicastRemoteObject class and implements the IRemoteResultSet interface. (Figure 4 shows its class and interface relationships.) The IRemoteResultSet interface's methods retrieve JDBC ResultSet rows and close the ResultSet object. The RemoteResultSetImpl class also encapsulates the JDBC ResultSet object passed to it in its constructor.

The getNextRow() method returns one ResultSet row in an array of Objects to the client JWResultSet. It returns null if ResultSet does not have any more rows:

public Object[] getNextRow() throws RemoteException,SQLException
                                                                              {
                                                                                 // Return null if all data has already been iterated
                                                                                 if(sqlRs.next() == false)
                                                                                    return null;
                                                                                 // Prepare the data row in an array of Objects
                                                                                 Object []row = new Object[colNum];
                                                                                 for(int i = 1; i <= colNum; i++)
                                                                                 {
                                                                                    row[i-1] = sqlRs.getString(i);
                                                                                 }
                                                                                 return row;
                                                                              }
                                                                           
                                    

The close() method closes the JDBC ResultSet object:

public void close()
                                                                              throws RemoteException,SQLException
                                                                              {
                                                                                 sqlRs.close();
                                                                              }
                                                                           
                                    

That's all there is to the middle-tier classes.

Page 3 of 3

Type 3 to the rescue

In this article, we showed you our type 3 JDBC driver's architecture. The driver constitutes both client- and middle-tier components, and it employs RMI to communicate between the client and server tiers. We also demonstrated how the client tier's various objects maintain reference to the corresponding remote objects in the server tier.

In Part 2, we will show how to use and deploy the type 3 driver created here. We'll offer a sample application and sequence diagrams to show how a driver loads, how to retrieve a JDBC Connection, and how to create a JDBC Statement.

Nitin Nanda is the associate project manager in Quark's research and development center based in Chandigarh, India. He manages the front office suite of components for a CRM product. Nitin's writing credits include: Professional Java Data and Beginning Java Databases, both from Wrox Press. He worked with Cadence Design Systems prior to joining Quark. Sunil Kumar is associate team lead at Quark's research and development center based in Chandigarh, India. He designs and develops the various components in a CRM product being engineered in VJ++/ASP/COM+/SQL Server. He worked with RAMCO Systems, developing generic ERP software, prior to joining Quark.

Learn more about this topic