A Java Data Objects Tutorial

by Don Denoncourt

Java Data Objects (JDO) is the latest option for an object persistence strategy. Learning how to use JDO can help you design applications more easily than you can with Enterprise JavaBeans (EJBs). To that end, I provide this basic tutorial on JDO and delve into some of its details. I'll show how to make a simple object model persistent. Once you've generated the database schema from the object model, we'll examine the example application to learn how to use JDO transactions and build JDO queries. Finally, I'll show you how to use existing databases with JDO. (For an overview of JDO technology, see "JDO for Entity Persistence," WebSphere Professional, January 2002.)

In this article, I use TechTrader's Kodo version 2.2.1 beta (http://www.techtrader.com/) as the JDO provider, but you can use your provider of choice. To learn more about JDO specifications, you can download Sun's latest reference implementation at http://access1.sun.com/jdo.

Persistent Capable

Our object model consists of three simple classes: Customer, Consumer, and Invoice (Figure 1, Figure 2, and Figure 3, respectively). Note two items in the classes' code implementations: (1) they contain absolutely no persistence code, and (2) Consumer is a subclass of Customer, which contains a collection of Invoice objects. You can compile and test the three classes, but to make them persistent, you must first enhance them and then register them with your database schema.

Enhancement is a JDO term that describes the process of adding persistent functionality to existing classes. JDO providers use as input to the enhance process existing Java source or their associated Java bytecode (.class) files and output, with the added persistent methods of either Java source code or Java bytecode (.class). Kodo creates new, persistent-capable versions of existing Java classes. Most JDO provider enhancement tools require an XML-based descriptor file that qualifies the package and class names.

Figure 4 shows a listing of my application's JDO persistence descriptor files, which I named package.jdo (.jdo being a suffix convention for the descriptors). For simple classes with no associations, such as Invoice, you must qualify only their name in the class XML tag. The Customer class, however, must have the field tag sub-element to describe the collection of Invoice objects that it contains. The XML class tag for Consumer includes the persistence-capable superclass attribute that qualifies the Customer class. Note that the JDO specification is rich enough to handle many other relationships other than the simple ones I show in this article.

With the JDO XML descriptor available, you can enhance your application. Kodo has a command line utility called jdoc (for JDO compiler) to which you qualify the location and name of the JDO descriptor, as follows:

jdoc com/webspherepro/jdoexample/package.jdo

Alternatively, I can use the actual Kodo enhancement class name:

java com.solarmetric.kodo.enhance.JDOEnhancer com/webspherepro/jdoexample/package.jdo

Figure 5 illustrates how the enhancer modifies the Java class files. This code contains the output from javap and lists more than two dozen methods of the Invoice class - yet the source for the Invoice class in Figure 3 implements only half a dozen methods! Figure 5 proves that after JDO enhancement, the Invoice class provides implementations of the javax.jdo.PersistenceCapable interface. You should investigate this interface in detail, but for now, know that after enhancement, the Customer, Consumer, and Invoice classes are "persistent capable."

The Persistence Schema

Once you've enhanced the classes, you must create the database for object persistence. To that end, Kodo provides a tool appropriately called schematool, which you run by qualifying the name of the JDO XML descriptor:

schematool -action refresh com/webspherepro/jdoexample/package.jdo

Optionally, you can directly use the Kodo class that implements JDO schema generation:

java com.solarmetric.kodo.impl.jdbc.schema.SchemaTool  -action refresh
com/webspherepro/jdoexample/package.jdo

For a schema tool to know which database you're running, your JDO provider must identify the type of database, driver names and profiles, and so forth that you're using. After all, JDO isn't relational database specific. (Note: Sun's JDO reference implementation supports text-based databases and relational databases.)

Figure 6 shows how I identified IBM's DB2 Universal Database for iSeries (DB2 UDB) as my persistence database to Kodo. Like most other available JDO providers, Kodo uses JDBC 2.0 DataSources to pool connections.

After the JDO provider has identified the database, the schema tool can generate the necessary relational database files (Figure 7). The schema tool uses a metadata file that keeps track of all persistent-capable classes. Inserts to the JDO_SCHEMA_METADATAX table identify each of the three classes in my applications. Each persistence table contains two columns: JDOIDX and JDOCLASSX. JDOIDX is a universal identifier that uniquely identifies rows of a database and their relationship to object instances. JDOCLASSX qualifies the name of the class implementation. For instance, through the JDOCLASSX column, Consumer instances are identified from within the CUSTOMERX table.

Managing Persistence

Once you've enhanced the classes and generated the database schema, the application can begin to populate the database. The Customer, Consumer, and Invoice classes have no code that says when to persist - this is where the CustomerMaintenance class (Figure 8) comes into play. This class contains methods to add, list, and remove instances (I'll explain these implementations later). The engine, so to speak, of JDO is javax.jdo.PersistenceManager. You populate, retrieve, and remove persistent-capable objects from your database via a PersistenceManager instance. The JDO specification identifies a PersistentManagerFactory class; I use Kodo's Singleton class called JDOFactory (Figure 9) to retrieve a PersistenceManager from a static PersistenceManagerFactory object instance. Note that for production applications, you should put an instance of the PersistenceManagerFactory in a Java Naming and Directory Interface (JNDI) context. Also note that, somewhat like a JDBC connection, a PersistenceManager maintains only one transaction at a time. Thus, the CustomerMaintenance class's use of a static PersistenceManager won't work in a servlet.

Figure 10 shows the output from a test of CustomerMaintenance's main method. Let's examine each of the 10 steps and how they work.

Step 1. Add five Customer instances. Referring back to Figure 8, the addCustomer method introduces you to JDO transactions. Persistent-capable classes are made persistent while in a JDO transaction context. JDO transaction contexts are retrieved from a PersistenceManager with its currentTransaction method. The transaction has a begin and commit, with a call to PersistenceManager's makePersistent method inside the scope of the transaction block.

One of the five Customer objects instantiated is a Consumer object, which was created with a call to the addConsumer method. Note that both the addCustomer and addConsumer methods return an object that wrappers the JDOIDX of the Customer (or Customer subclass) object just added to the database.

Step 2. Use an object ID to retrieve a Customer object. Using a call to PersistenceManager's getObjectById method and employing the object ID returned from addCustomer, you can retrieve a specific Customer object instance from the database (but rather than forcing a database read, the Customer object may be retrieved from the JDO cache).

Step 3. Add an invoice to the Customer object. After an Invoice object is instantiated, it's passed to CustomerMaintenance's addInvoice method along with the instance variable of the Customer object. The addInvoice method simply establishes a transaction context, adds the invoice to the Customer object, persists the change, and then commits the transaction.

Steps 4 and 5. List all Customer and Consumer class instances. The listAllCustomers and listAllConsumers methods introduce JDO extents, which are analogous to SQL table indexes. Data is retrieved from the extent only after the Query object execute method is invoked and records are accessed from the Iterator of the returned collection object. A persistent-capable object's extent is retrieved from the PersistenceManager by calling the getExtent method and passing the class as an argument. Note that the second parameter to getExtent is a Boolean value that says whether or not you want the extent to contain subclasses. For example, listAllCustomers passes a true Boolean value so the extent contains Consumer objects, whereas listAllConsumers passes a false value (this is moot because Consumer has no subclasses).

After the listAllCustomers and listAllConsumers methods retrieve an extent from the PersistenceManager, they simply spin through an Iterator, listing the complete contents of the persistent database. In production applications, however, you'll rarely spin through all instances of a database.

Step 6. List all Customer class instances with a specific state value. CustomerMaintenance's listCustomers method uses a JDO query to retrieve a subset of Customers based on state. A JDO Query object is retrieved from the PersistenceManager with the newQuery method. This method is overloaded; one variation takes Class and Extent objects as arguments, whereas another variation requires Class, Extent, and a String filter. Filters contain selection criterion using Java-like syntax (instead of SQL Where clause syntax). Note that to remove the kludgy use of escaped double-quotation marks, you can also employ parameterized JDO queries. After the Query object is returned from the PersistenceManager, listCustomers invokes the Query object's execute method, which returns a collection and is subsequently iterated.

Step 7. Delete a specific Customer instance. You can delete persistent instances based on an object instance, a collection, or an array using one of three PersistenceManager methods:

deletePersistent(Object pc)
deletePersistentAll(Collection pcs)
deletePersistentAll(Object[] pcs)

CustomerMaintenance's deleteCustomerById method retrieves a collection of Customer object IDs using a JDO query and then deletes all instances in that collection. Note that, in this example, the query on the customer ID will return only a collection of one. Also note that a transaction context is required.

Step 8. List all instances of the Customer class. This step lets you verify that the Customer was removed.

Step 9. Delete all instances of the Customer class. To empty the complete database of Customer objects in CustomerMaintenance's deleteAllCustomers method, you (1) retrieve a query without a subsetting filter, and (2) invoke the deletePersistentAll method passing the collection. Again, a transaction context is required to perform the deletions.

Step 10. List all instances of the Customer class. Performing this step lets you verify that all instances of the Customer class have been deleted.

Using Existing Databases with JDO

Although creating databases from an object model is a clean, object-oriented strategy, many of us have existing databases that we must wrapper from Java classes. Never fear, you can do that with JDO. The following Create Table statement illustrates a poorly designed database file, which I will show how to access with JDO:

CREATE TABLE QCUSTCDT
(
        CUSNUM NUMERIC(6, 0), LSTNAM CHAR(8), INIT CHAR(3),
         STREET CHAR(13), CITY CHAR(6), STATE CHAR(2),
         ZIPCOD NUMERIC (5, 0), CDTLMT NUMERIC (4, 0),
        CHGCOD NUMERIC (1,0), BALDUE DECIMAL (6, 2),
        CDTDUE NUMERIC (6, 2)
)

Figure 11 shows the Java class that represents this database table. One problem when using an existing table is that JDO no longer has the benefit of the user ID, as does the table in Figure 7 that includes JDOIDX. The solution is to create a key class such as the one in Figure 12.

In the JDO XML descriptor (Figure 13), the identity type and objectid class attributes in the class tag tell the PersistenceManager how to uniquely identify a customer from a CustomerKey object. Kodo-specific extensions are then used to map class fields with database columns. With the JDO XML descriptor available, you run enhancement and then register the classes with JDO's schema. Doing so makes the Customer class persistent capable to a legacy database.

Lightweight Entity Beans

Because of JDO's ease of use, developers are quickly embracing this hot new technology. JDO removes most of the cost and complexity involved in using EJB as a persistence engine, while providing greater flexibility. JDO seems a given for small shops, but large IT departments are also embracing JDO. SAP, for instance, has announced that it's adopting JDO technology. And the list of JDO providers is growing every month. I expect the adoption rate of JDO to skyrocket in early 2002. As for my clients, I'll be recommending they use JDO as a lightweight alternative to EJB entity beans.

Don Denoncourt is the author of Java Application Strategies for the AS/400 (Midrange Computing, 2001) and has taught more than 1,000 hours of Java seminars to AS/400 and iSeries programmers in various cities across the country. You can e-mail Don at denonco@attglobal.net.




Copyright © 2003 - Penton Technology Media