Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Java Proxies for Database Objects


May99: Java Proxies for Database Objects

Paul is director for object technology at Computer Associates and a technical representative to the Object Data Management Group. He can be reached at [email protected].


Relational databases require a veneer of mapping code to translate the state of Java objects to their spreadsheet-like world of rows and columns. In object-oriented terms, the fundamental unit of manipulation for relational databases is the instance property, not the object. In other words, SQL programmers have to spend a lot of time thinking about the table column, which object-oriented programmers think of as the instance property and not the object itself. In many ways, they are forced to work in a unique, artificial, two-dimensional world. Object-oriented developers, on the other hand, build complex three-dimensional networks of objects in an effort to model the real world. This model, like the real world, explicitly represents complex one-to-many and many-to-many relationships, such as collections.

Object databases, by definition, store and retrieve objects. The fundamental unit of manipulation throughout the application and the object database architecture is consistently the object. Thus, object databases let you avoid the added complexity, risk of error, and overhead of mapping code to translate between the world of objects, and the spreadsheet-like world of the relational database.

How do object databases work with Java? In many cases, Java object persistence for object databases is handled effectively by an object-level Java language binding. Usually, this binding is based upon the Object Data Management Group's (ODMG) Java language binding specification. The ODMG is an industry consortium of object database, object-relational mapping software, and application server vendors. Unlike an older, API-based approach to databases such as JDBC, the ODMG's tight language binding preserves the Java object model.

Most importantly, this language binding lets Java programmers access a database without having to leave the Java universe; no SQL or deep understanding of database specifics is required. In fact, developers are usually expected to define database object schema within their client Java programs as Java class definitions. This is often extremely useful, especially when developers are struck with the stunning realization that they need to store and retrieve the state of certain Java objects at run time. This profound realization often occurs after much code has been written!

However, as the ODMG approach to Java object persistence currently stands, there is no explicit mechanism for taking advantage of advanced object databases that can execute methods on the database server, as well as in the Java client. The ODMG standard assumes that methods are always executed on the client's Java Virtual Machine (JVM). The ODMG bindings also do not have any special support for multimedia. Another concern is that typical users of the ODMG binding do not often maintain database schema by using the database's native Object Definition Language (ODL). Rather, you are usually expected to use Java to define database object schema within your client Java programs.

This might be acceptable, or even desirable, in an application system where all the code is written in Java, but Java developers often need to access and update database objects created by a wide range of applications already written in different languages, such as C++, Delphi, or Visual Basic. This is particularly true in large enterprises. Obviously, defining and maintaining the schema for such objects in the source code of one particular application, such as a Java program, is like the tail wagging the dog. It makes more sense, under these circumstances, to store and maintain database schema definitions using the database ODL. Java proxy technology is the answer to the issues of explicit support of unique database features and centralized schema definition.

Java Proxy Technology

Java proxy technology can allow developers to define database object schema using the database ODL. To illustrate how such a technology might be implemented, and to describe how to use such an approach, I will provide examples based on Jasmine, the object-oriented database from Computer Associates (the company I work for).

Jasmine contains an object database with support for server- and client-side methods, along with a GUI development environment, and support for ActiveX, HTML, C/C++, Java, and more. For those who choose to develop applications using Java, Jasmine includes two components. One component, "pJ" (short for "persistent Java"), is a complete implementation of the ODMG Java language binding, and includes support for the Object Query Language (OQL), an important part of the ODMG specification that vendors often fail to implement. It is the official ODMG way to do queries at the object level, and is similar to SQL-92 with object-oriented extensions. By supplying both pJ and OQL, Jasmine complies with the ODMG standard for Java.

However, persistent Java is only one side of the equation. While designing Jasmine, it became clear that it was also necessary to provide Java proxy support so that Java clients could use Jasmine's advanced features such as server-side methods and integrated multimedia support. Also, it was desirable to allow definition of database object schema using Jasmine's ODL, called Object Database Query Language (ODQL), if required. In Jasmine, the Java proxy support is, appropriately, in the component called "Jp" ("Java proxy"), which comes with Jasmine.

The idea of a Java proxy is that an object in the client JVM represents (or proxies) the object in the database itself. The Java proxy is an extremely thin, almost stateless object, well suited to Internet deployment. It completely encapsulates the object it represents in the database.

Reverse Java Language Bindings

The reverse language binding (generating database-aware Java classes from database classes) is based on Java proxy technology in Jasmine. To generate a Java proxy reverse language binding class definition in Java, one or more database classes is selected for processing by a Java program, called "JPCG" (short for "Java Proxy Class Generator"), which generates Java source code. JPCG reads database schema for one or more database classes, and generates a 100 percent pure Java class definition for each Jasmine class. Each Java class definition is a JavaBean that is also a Java proxy. Any database class- or instance-level property can be retrieved and updated in the database by using the Bean's get and set methods.

Consider Listing One, which is a Customer Java object, bound to the database Customer class by JPCG. For this example, the Customer database class has been simplified to have only one property, and one class-level (static) method. The get and set methods in the Bean encapsulate the CustomerNumber instance property of Customer objects in the database. You can see how the database data types relate to Java data types in methods generated by JPCG. In this example, there are two methods available to map properties directly to Java primitives, required for a JavaBean. Such database NIL values are simply mapped to primitive values that make sense, like zero.

The getLowCreditRisks method in Listing One is also generated by JPCG and maps to the equivalent method in the database's Customer class. It shows that calls to the methods of a JPCG generated JavaBean on the client automatically result in the appropriate server-side methods being called via that Bean's proxy. On the client, the method's parameters are marshaled and passed to the database object or class that the proxy is representing where the equivalent method is executed on the database server. The results of the method's execution are returned to the client JVM. The Java proxy object on the client JVM appears to have executed the method locally. The calling Java object is never aware that the method was actually executed on the server.

The advantage, of course, is that server-side logic can be secured and scaled as necessary, so that sensitive or processor intensive logic need not be downloaded to the client. Also, complex commercial libraries that would be too expensive or large to download to a client can be accessed and used on the server. Jasmine allows compiled logic residing in shared libraries on the server to be used, resulting in high-performance shared logic running close to the data.

The createCustomer method creates an object of type DBObject to serve as the underlying proxy for this Bean. Every JPCG generated Bean can access the methods in its proxy object directly by using its toDBObject method. DBObject has two more methods that use or return Jasmine data types that are mapped to java.lang.* objects. This is so that NIL property values in Jasmine can be mapped to a null value in Java. It is also possible, using a proxy's isPropertyNil method, to ascertain if a property is NIL without retrieving its value. Special methods are available for each proxy to allow multiple properties to be obtained and changed for a database object. This helps minimize network traffic. Constructors, destructors, and various other utility methods are also available in a proxy.

Dynamic Java Proxies

The ability to statically bind a Jasmine database class directly to an equivalent generated Java proxy class using JPCG can be useful, especially for applications that depend on specific, known classes. However, there are times when database access may need to be dynamic. Tools such as report writers may need to access classes that did not even exist at the time that they were written.

Jp lets a number of different Java proxy objects be created dynamically. Some of these objects proxy database objects or classes, while others encapsulate related database functions such as support for queries, dynamic database schema access, or database session and transaction control. This dynamic portion of Jp is referred to as J-API.

There are six basic classes in J-API: Database, DBObject, DBClass, DBCollection, ODQLStatement, and Database- MetaData. The Database class represents the database itself. Each Database object contains methods that can connect to the database, establish a database session, and establish either a client/server or three-tier connection to the database. Multiple Database objects can be instantiated within an application, each fully encapsulating a client/server or three-tier session connection and session with a Jasmine database. Database transactions for each session are also controlled and their state is examined via Database instance methods, such as rollbackTransaction, that are defined for the Database class.

A Database object also contains convenient factory methods for all the other classes in J-API. The advantage of using the Database object's factory methods, instead of the constructor for the other classes in J-API like DBObject, is that the factory methods allow the instantiation of proxies that are automatically associated with the correct database session. Usually, the less convenient alternative is to specify the Database object in a class's constructor to create this association. This encapsulation of the database session, and all other aspects of the Jasmine database, is an important principle in the design of Java proxies. To maximize performance, Jp is designed and optimized to fit tightly with the Jasmine object database server architecture.

The proxy classes that encapsulate actual database objects are, of course, of primary concern here. The DBObject and DBClass classes are merely instance-level and class-level (static) versions of the same basic idea. Each of these classes has the same kinds of methods. An instance of DBObject encapsulates the instance-level database object's properties and methods while an instance of DBClass does the same for a database object's class-level properties and methods.

Since it doesn't really matter which of these two proxy classes I choose, let's look at DBObject. To be a proxy, an instance of DBObject must be associated with a database object. This is done by setting a DBObject to the object identifier (OID) of a particular object in the database. An OID is a logical value that uniquely identifies an object in the database. An object's OID is immutable in Jasmine. In Jp, an OID is represented as a String.

In designing Jp, an effort was made to provide a rich set of methods for DBObject creation and association with Jasmine database objects. For example, it is possible to use the earlier mentioned factory methods to instantiate a database object on the database server, and associate it with a newly created Java proxy object on the client JVM at the same time. The method in Example 1 would accept the name of a database class family (similar to a package in Java) and class name, and return a DBObject that proxies a newly created database object. Once the database transaction is committed, that object becomes persistent within the database. A DatabaseException, extended from java.lang.Exception, can be thrown for any proxy method that can return a database or communication error. Of course, methods exist to obtain error text, as well as to determine the type of error.

Listing Two is a fully functional Java program that creates a new Customer object in the database, and proxies it with a newly created Java proxy assigned to the reference variable dbo. Once the program commits the database transaction with the endTransaction method, the newly created Customer object is permanent unless explicitly deleted. Had the rollbackTransaction method been called instead, the database would be brought back to the state it was originally, as if the createDBObject method had never been invoked.

A variation of the createDBObject method allows the specification of property values at database object creation time. The values are passed in as key-value pairs using a hash table. The getDBObject method accepts an OID, and returns the appropriate type of J-API object: DBObject, DBClass, or DBCollection.

It is important to be able to dynamically change the database object that a DBObject is proxy to. The setOID method allows a DBObject to be a proxy to more than one database object in its lifetime, thus avoiding the overhead of Java object creation. The getOID method returns the OID for an instance of DBObject.

Collections and Server-Side Logic

The DBCollection class encapsulates the concept of a collection (array, bag, list, set). Collections can contain objects or atomic literals. They can be the properties of persistent database objects (and thus persistent themselves), or they may be temporary.

The idea of a temporary collection is tied to how Jasmine implements a database session. In Jasmine, the concept of a database session is more than just a connection between client and server. The Jasmine engine is multithreaded, and each database session has associated with it an execution thread that contains its own server-side run-time environment.

Each session run-time environment is where server-side logic executes for a client. Thus, when server-side methods are called from a Java proxy, the method runs in its own thread on the server. The ODQL interpreted database language is always available in the server as part of the database session thread. The language is similar to C++ with extensions that make it object database aware. Its usefulness comes from the unique synergy of its three identifying characteristics: It is a true database language, computationally complete, and object oriented.

Listing Three contains Java client code that calls a compiled server-side method called getLowCreditRisks. Jasmine allows server-side methods to be compiled in ODQL and/or C++ in the Jasmine server. Compiled ODQL is merely preprocessed into C++, so that ODQL methods can take advantage of compiler optimization technology from any of the popular C++ compilers for various platforms, such as Windows NT or Solaris.

This server-side method can do a lot of filtering and refinement, so that the client need only receive a small subset of total objects available in the database. This is in stark contrast to older object database technologies that require all objects to be moved to the client for processing. This was a key design goal for Jasmine allowing network traffic to be considerably minimized. Scalability is also improved because the power of the server, obviously, is more under our control than the client may be.

Listing Three uses the Java proxy method called execMethod to invoke a server-side method called getLowCredit-Risks. The proxy's execMethod logic marshals the parameters, if any, and passes them on via RMI to the class-level method on the server called getLowCreditRisks. The getLowCreditRisks method on the server does some work and returns a temporary collection on the server, which is proxied by the DBCollection called dblist on the JVM. The dbclass proxy method called execMethod returns a DBCollection to the dblist variable, which proxies the returned temporary collection that remained on the server.

The DBCollection proxy class allows enumeration of the collection. Only during enumeration are any objects actually brought down from the server to the client. Objects are brought down in blocks. The size of these blocks can be programmatically controlled in order to let you optimize both network traffic and JVM memory utilization.

Because DBObject fully supports the DataSource interface of JMF (Java Media Framework), I can access each customer's picture by simply streaming the image. JMF is a Java Standard Extension for the JavaBeans Activation Framework. The Datasource interface fully encapsulates the underlying data in an object and provides a common interface to access it. The methods are very simple to use. The getContentType method returns the MIME type of the data in the object. The DataSource interface consists of:

InputStream getInputStream();

OutputStream getOutputStream();

String getContentType();

I use DBObject's getInputStream method in Listing Three to stream in the bitmap photograph of each customer from the database. First, I make the dbo variable refer to a DBObject proxy of the multimedia object referred to by the customer object's photo property by using the getProperty method. Then, I invoke the DBObject's getInputStream method to open an input stream for the multimedia that is simply streamed out to a disk file. Any multimedia data can be so streamed.

Conclusion

Using a Java proxy approach to object databases is straightforward and understandable. Both the ODMG language binding and the Java proxy technology offer a higher level, more natural way of dealing with database objects than the JDBC API.

For Java applications that do not have to fit into an existing infrastructure of applications written in other languages and are designed to treat the database as a passive object repository with all the logic in the client JVM, the ODMG Java bindings are a good fit.

Java proxy technology can add significant value when the applications are based on a thin client approach with database objects that support server-side logic as well as object state. They also make sense for applications that plan to make heavy use of multimedia, or that need to fit into existing multilanguage solutions.

DDJ

Listing One

// Machine generated
package jp.jasmine.CAStore;

import jp.jasmine.japi.*;
import java.math.BigDecimal;
import java.sql.Date;
import java.rmi.*;
import java.util.*;

public class Customer  extends Person {
   protected static DBClass dbc;
   public Customer(Database db, String oid) throws DatabaseException {
      super(db,oid);
   }
   public Customer() throws DatabaseException {
   }
   public static Customer createCustomer(Database db) 
                                           throws DatabaseException {
      Hashtable arg = new Hashtable();

      DBObject dbo = db.createDBObject("CAStore", "Customer", arg);
      return (Customer) toApplicationObject(dbo);
   }
   private static void setDBClass(Database db) throws DatabaseException {
      if (db==null) throw new 
                       DatabaseException("Null database in setDBClass()");
      if (dbc==null) dbc = db.getDBClass("CAStore", "Customer");
   }
   // -------------------- Properties --------------------
   public int getCustomernumber() throws DatabaseException {
      return (dbo==null) ? 0 : dbo.getIntProperty("customernumber");
   }
   public void setCustomernumber(int customernumber) 
                                            throws DatabaseException {
      if (dbo!=null) dbo.setProperty("customernumber", customernumber);
   }
   // -------------------- Methods -----------------------
   public static DBCollection getLowCreditRisks( Database db ) 
                                              throws DatabaseException {
      if (dbc==null) setDBClass(db);
      Object arg[] = {};
      Object o = null;
      if(dbc!=null)
         o=dbc.execMethod("getLowCreditRisks", arg, 
                                              "List<CAStore::Customer>");
      return (DBCollection) o;
   }
}

Back to Article

Listing Two

import jp.jasmine.japi.*;
import java.io.*;

public class example2
{
  public static void main(String args[]) {
    try {
        Database db;
        // Connect to database as 3-tier application and start a session.
        db=new Database("pcl.cai.com", 1099);
        db.startSession();
        db.startTransaction();

        // Create a database object and proxy it in one call.
        DBObject dbo = db.createDBObject("CAStore", "Customer");

        db.endTransaction();
        db.endSession();
    }
    // Simple exception handling.
    catch( Exception ex) {
        System.out.println("test error: " + ex.toString());
        ex.printStackTrace();
    } 
  }

}

Back to Article

Listing Three

import jp.jasmine.japi.*;
import java.io.*;
import java.util.*;

public class example3
{
  public static void main(String args[]) {
    try {
        int aChar;
        String customerName;
        Database db;
        DBObject dbo;

        db=new Database("pcl.cai.com", 1099);   // 1099 is the port number
        db.startSession();
        db.startTransaction();

        // Create a proxy for new temporary database collection of customers.
        DBCollection dblist = 
                db.createDBCollection("List", "CAStore::Customer", null);
        // Proxy the customer class (CAStore is like a Jasmine "package").
        DBClass dbclass = db.getDBClass("CAStore", "Customer");
        
        // Call a server-side method to query, call credit bureaus, etc.
        dblist = (DBCollection) dbclass.execMethod("getLowCreditRisks", 
                                          null, "Bag <CAStore::Customer>");
        // Print name of each low credit risk customer & dump photo to disk.
        Enumeration e = dblist.elements();
        for (; e.hasMoreElements(); ) {
            dbo = (DBObject) e.nextElement();
            customerName = (String) dbo.getProperty("name");
            dbo = (DBObject) dbo.getProperty("photo");
            if (dbo == null) 
                System.out.println("Name: " + customerName + 
                                                 " image not available!");
            else {

                System.out.println("Name: " + customerName + "  File: " 
                                                  + customerName + ".bmp");
                // Stream the multimedia object that we proxy to disk.
                InputStream is = dbo.getInputStream();
                OutputStream fos = new BufferedOutputStream(new 
                                  FileOutputStream(customerName + ".bmp"));
                while((aChar = is.read()) >= 0) 
                    fos.write(aChar);
                fos.close();
                is.close();
            }
        }
        db.endTransaction();
        db.endSession();
    }
    // Simple exception handling.
    catch( Exception ex) {
        System.out.println("test error: " + ex.toString());
        ex.printStackTrace();
    } 
  }
}


Back to Article


Copyright © 1999, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.