JDBC via CORBA

by Marlin W. Pierce <marlinp@xdb.com>

Complete source for this example: corbajdbc.tar corbajdbc.zip

Some people are trying to get JDBC to go through CORBA and ultimately over IIOP.  After harping about how this will inherently be slow, I decided to post instructions on how to this, so people would spend little time discovering how bad it was.  However, when I worked on the demonstration, it turned out to be reasonably fast at least for a single user going over IIOP between two NT hosts on an Ethernet LAN.

I am willing to admit when I am wrong, but I'm not ready to eat my words yet.  Sometimes I notice some lag, and a single user over a fast LAN is no demonstration that this has an acceptable performance. However, my reasons for posting this now include showing people how to do this, as well as helping people come to a quicker conclusion on when it won't be acceptable.  My only fear is that this demo will mislead people into thinking it is acceptable, when it is really slow out in the field.

Nonetheless, I am publishing the instructions to help others do what they are trying to do.

The Architecture

We are going to write a JDBC Driver, which resolves all of its calls by going through CORBA to CORBA services which call some other JDBC Driver.

For simplicity, I will only work with enough of the Connection, Statement, and ResultSet interfaces to return the results from a query. Completing the rest of JDBC is an exercise left to the reader.  We will also add a sayhello() method, to test the creation of each object as we go.

On both the Server and Client side, we will delegate the calls for our server side Services and client side JDBC Driver to another related object.  The Services will delegate the calls to a JDBC object.  The Client JDBC Driver will delegate the calls to the CORBA references.

The reasons I used delegation are:

  1. There was a conflict on some of the methods which return other Objects I am defining.  For instance methods like Connection.createStatement() in the CORBA and JDBC classes had the same name and arguments, but different return types.  This could have been avoided by using different method names in the IDL for these problem methods.
  2. To implement the Services, I would have had to change the code of the JDBC Driver to extend the skeleton.  I could have done this since I had the source for my Dummy JDBC Driver, but most people wouldn't have the source for the JDBC Driver they wanted to use.
  3. By using delegation for the Services, this code will work with any JDBC Driver on the Server side.  In fact, I can register all the Drivers accessible from that ORB, and the URL will select the needed Driver.
  4. On the Client side, I would have to get the stubs to implement the JDBC interfaces.  Unless I was willing to modify the stubs which the idltojava preprocessor generates, I would have to extend the stubs to a class which implemented the JDBC interfaces.  However, then how do I get one stub to return my extension instead of the IDL generated stub I am extending?

The IDL

First of all, we will need the IDL for the services we will be implementing.  If we are careful, the client stubs generated will
directly implement the JDBC methods, so we will not have to do much more than extending the stubs to implement the JDBC Interfaces.

One thing we notice is that CORBA does not have constructors. Furthermore, JDBC deals with a DriverManager.  Creating the original Connection object will take a little thought.

The ResultSet object is created from the Statement object with the Statement.executeQuery(String) method.  Likewise, the Statement object is created from the Connection object from the Connection.createStatement() method.  What we will do via CORBA is create another "factory" object, ConnectionFactory, which will have a method which creates Connection objects.  This will solve the bootstrapping problem for the IDL.  (The DriverManager solution will be discussed later.)

For our stripped-down demo, the IDL will be the following:

    module corbajdbc
    {
      interface CorbaResultSet
      {
        boolean next();
        string  getString(in long iColumn);
        string  sayhello();
      };

      interface CorbaStatement
      {
        CorbaResultSet executeQuery(in string sSQL);
        string  sayhello();
      };

      interface CorbaConnection
      {
        CorbaStatement createStatement();
        string  sayhello();
      };

      interface CorbaConnectionFactory
      {
        CorbaConnection connect(in string sURL,
                                in string sUser,
                                in string sPassword);
        string  sayhello();
      };
    };
 

Here we see that the ConnectionFactory has a method, connect(), which takes all the information which will be needed to call the
DriverManager to get a Connection object.

An Example JDBC Driver

Before going into the CORBA implementation, I will introduce an example JDBC Driver.  This Driver has primitive methods to implement the JDBC interfaces, however, it does not return meaningful data. Since it returns a string for the getString() method, we can use it for our CORBA implementation.

One purpose of this driver is testing the performance issues of CORBA alone.  Since getString() returns a String immediately, all the time in returning the results on the client are due to going through CORBA. The time it takes for an actual database to generate the results of a query is eliminated.

The other purposes for this example JDBC Driver, is that it will be helpful in implementing the Services, since the method calls are
provided, even if the method bodies are not significant.  On the Client side, this example JDBC Driver will be helpful for our demo by allowing us to implement the JDBC interfaces when we are only supplying a small subset of the JDBC API.

For our purposes, we will implement the Connection, Statement, and ResultSet interfaces.  We will also need a implementation of the Driver interface to create the Connection object in a JDBC way.  Simple implementations of these, which are enough to satisfy the implements requirement are the following:

Source code for the dummy driver: DummyDriver, DummyResultSet, DummyStatement, DummyConnection

Implementing the Services

Next, we will implement the CORBA Services.

First we need to compile the IDL into the server side code.  From the command line, we will run idltojava as follows, assuming we created a file named jdbc.idl from the IDL above:

    idltojava -fserver jdbc.idl
    idltojava -fclient jdbc.idl

This will create a corbajdbc subdirectory with a number of Server side Java files.

Implementing the ResultSet Services

Next, to implement the ResultSet services, we will create a ResultSetServant Java file.  Our ResultSetServant will take a JDBC
ResultSet object, and delegate the calls to it.  Thus the ResultSetServant will look like the following:

Source code for ResultSetServant.java

I didn't deal with sending the SQLExceptions across to the Client.  That is left as an exercise for the reader.

I did, however, leave the import statements for the packages which would be necessary to finish this class for the full ResultSet API.

Implementing the Statement and Connection Interfaces

Next, the Statement and Connection interfaces are handled the same way. However, when they return a ResultSet and a Statement respectively, they will actually return a ResultSetServant and a StatementServant.

Notice that these classes cannot implement the JDBC Statement and Connection interfaces because they return different object types for executeQuery() and createStatement().  Thus these servants cannot also be a JDBC Driver.

Source code for StatementServant and ConnnectionServant

Implementing ConnectionFactoryServant

Next, we have the ConnectionFactory to implement.  Unlike the previous servants, the ConnectionFactory does not look like a JDBC class.  The critial method in this Factory object is the connect method which returns a ConnectionServant.

First, all the Drivers which are supported by this service are registered, once, when this class is referenced and loaded, from the static code block.  Then, when the connect() method is called, the ConnectionFactory calls the DriverManager.getConnection() method.  The connection returned from this call is used for the constructor to the ConnectionServant.

Source code for ConnectionFactoryServant

The JDBCServer

Finally, we need a server to start the ORB, and bind the ConnectionFactory service in the nameserver.  This code looks like the following:

Source code for JDBCServer

Implementing the Client JDBC Driver

For the Client side, first we must generate the Client stubs.  From the command line, we will run idltojava as follows, assuming we created a file named jdbc.idl from the IDL above:

    idltojava -fclient jdbc.idl

This will create a corbajdbc subdirectory with a number of Client side Java files.

Next, to implement the ResultSet services, we will create a C_ResultSet Java file.  Our C_ResultSet will take a Corba stub CorbaResultSet object, and delegate the calls to it.  Likewise, we will do the same for Statement and Connection.    Thus the files will look like the following:

Source code for C_ResultSet, C_Connection and C_Statement

Implementing C_Driver

Now, we need a JDBC Driver class.  A JDBC Driver class is supposed to (1) have a constructor with no arguments, and (2) register itself whenever the class is simply referenced.  To meet this goal simply, I hard coded the address of the ORB.  Thus, this driver can create multiple Connections, but all Connections go to the same ORB to find the JDBC Services.

We could have passed the address of the ORB through the Properties object.  However, you want to avoid creating an ORB reference for every Connection.  Remember, if there are multiple Connections, many will probably go to the same ORB.

This Driver also takes JDBC URLs starting with jdbc:corba:.  Then it passes the rest of the URL with the jdbc: prefix to the
ConnectionFactory.

Source code for C_Driver

Building the Programs

Included in the corbajdbc.tar file is a buildcorbajdbc script. This is a Bourne shell script which has been run on Solaris 2.5.1 with the Java IDL 1.1 FCS for Solaris and JDK 1.1.4. It assumes that both the JDK and Java IDL have been set up to be on the PATH, and that CLASSPATH contains at least ".". The buildcorbajdbc script also assumes that it is being run from the directory into which corbajdbc.tar was unpacked.

Those of you on Microsoft OSes are on your own at this point.

Before building the programs, you will have to alter the host name in C_Driver.java to match the name of the machine on which you intend to run the servers.

Running the Test Program

 The test program looks like any JDBC program.  The only thing is that since our Dummy driver always returns true for the ResultSet.next() call, we were careful to only fetch twenty records.

Source code for test program TestCorbaJDBC

After we compile all the Java files, we need to start the nameserver. Go to the bin directory where you installed the JavaIDL and run the following command:

    nameserv -ORBInitialPort 1080

Note: if you are not using the Java IDL 1.1 EA release, then use tnameserv rather than nameserv.

Next, start the corbajdbc services.  Go to the server and run the following command:

    java corbajdbc.JDBCServer -ORBInitialPort 1080

Finally go to the client and run the following command:

    java TestCorbaJDBC

For the convenience of Solaris users, a runcorbajdbc script is included in the corbajdbc.tar file.