An Intro to Java Object Persistence with JDO

toring and retrieving information for most applications usually involves some form of interaction with a relational database. This has presented a fundamental problem for developers for quite some time since the design of relational data and object-oriented instances share very different relationship structures within their respective environments. Relational databases are structured in a tabular configuration and object-oriented instances are typically structured in a hierarchical manner. This “impedance mismatch” has led to the development of several different object-persistence technologies attempting to bridge the gap between the relational world and the object-oriented world. The Java Data Objects (JDO) API provides yet another means for bridging this gap.

This article is the second in a series discussing how three different object-persistence technologies (EJB, Java Data Objects, and Hibernate) attempt to simplify the chore of connecting relational databases and the Java programming language.

Introducing Object Persistence
Persisting Java objects to a relational database is a unique challenge that involves serializing hierarchically-structured Java objects to a tabular-structured database and vice versa. This challenge is currently being addressed by a number of different tools. Most of these tools allow developers to instruct persistence engines as to how to convert Java objects to database columns/records and back. Essential to this effort is the need to map Java objects to database columns and records in a manner optimized for speed and efficiency.

The Java Data Objects (JDO) API provides a standard, straightforward way of achieving object persistence in Java technology. JDO uses a practical combination of XML metadata and bytecode enhancement to ease development complexity and overhead, as compared to other object binding technologies.

An Overview of JDO
The JDO API, consisting of just a few interfaces, is one of the simplest APIs to learn of all the standardized persistence technologies currently available. There are many implementations of the JDO API from which to choose. The JDO reference implementation is freely available from Sun.

One reason for JDO’s simplicity is that it allows you to work with plain old Java objects (POJOs) instead of proprietary APIs. JDO addresses most of the persistence augmentation in a post-compilation bytecode enhancement step, thus providing an abstraction layer between the application code and the persistence engine.

JDO has fallen under attack for a number of reasons, including: The code-enhancer model should be replaced by a transparent persistence model; vendors could lose their proprietary lock-in; JDO overlaps too much with EJBs and CMP. Despite these points of resistance, many experts are touting JDO as a vast improvement over the current trends in object-to-data and data-to-object technologies.

The Application and Runtime Environment
I will use the JDO reference implementation found at http://www.sun.com/software/communitysource/jdo/index.html and JBoss 3.2.3 as my deployment and runtime environment for the examples that follow. I will design a simple Web application that allows user accounts to be created and retrieved using a Web browser and show you how to do the same. Client requests will be passed from a browser to a Java servlet, which communicates with a user service, which communicates with JDO-based data access objects (DAOs), as Figure 1 illustrates.

Figure 1. The Four Tiers: A high-level view of the framework’s architecture is shown.

The DAO pattern abstracts and encapsulates all access to the data source. The application has one DAO interface, UserDao. The implementation class, JDOUserDao, contains JDO-specific logic to handle data-management duties for a given user.

The Web Tier Configuration
Each client HTTP request is handled by a FrontController-style servlet embodied within an instance of UserInfoServlet. The UserInfoServlet instance converts each request to a business-service request and then calls the appropriate business service for processing. The UserInfoServlet is shown in Listing 1.

The Business Tier
Each client HTTP request is converted to a business-service request and passed to the appropriate business service for processing. Each business service object performs the necessary business logic and makes use of the appropriate DAO for datastore access.

The UserService class encapsulates methods for operating on UserInfo objects including storing, updating, deleting, and retrieving instances of UserInfo. The UserService class is shown in Listing 2.

JDO Metadata Files
JDO uses XML-based metadata files to specify persistence-related information including which classes should be persistent. The metadata file can contain persistence information for a single persistent class or one or more packages that have persistent classes.

A JDO persistence-metadata file can define persistence properties?for a class or an entire package?in one or more XML files. The name of the metadata file for one class is the name of the class, followed by a .jdo suffix. Therefore, the metadata file for the UserInfo class would be named UserInfo.jdo. This must be placed in the same directory as the UserInfo.class file. Metadata for an entire package must be contained in a file named package.jdo. A .jdo file for an entire package can contain information for multiple classes and multiple sub-packages.

The following example illustrates how the UserInfo class representing a given user is configured for JDO-enhancement in the metadata file, package.jdo.

                                                                                                                                                                                                      

The Data Tier
Each business-service request is passed to the appropriate business service for processing. A business service performs the necessary business logic and makes use of the appropriate DAO for datastore access. Each DAO performs the necessary interactions with JDO in order to act upon a given datastore. The UserDAO interface defines the methods each DAO must implement:

package com.jeffhanson.datatier;import com.jeffhanson.businesstier.model.UserInfo;public interface UserDAO{   public UserInfo createUser(String id,                              String fullName,                              String address,                              String city,                              String state,                              String zip)      throws DAOException;   public UserInfo readUser(String id)      throws DAOException;   public UserInfo[] readUsersByState(String state)      throws DAOException;   public void updateUser(UserInfo userInfo)      throws DAOException;   public void deleteUser(UserInfo userInfo)      throws DAOException;}

The JDOUserDAO class provides an implementation of the UserDAO interface, which will enable access to the UserInfo object’s datastore.The JDOUserDAO Implementation Class
The JDO reference implementation comes with a file-based data storage mechanism called fostore. The JDOUserDAO implementation class uses the fostore persistence-manager factory. It stores data in two files named fostore.btd and fostore.btx.

Author’s Note: JDO supports a concept known as persistence-by-reachability. Persistence-by-reachability causes any transient instance of a class to become persistent when committed if the instance is reachable by a persistent instance.

Listing 3 illustrates the JDOUserDAO implementation class.

The JDO Bytecode Enhancer
A class must be enhanced before its instances can be managed by a JDO persistence engine. A JDO bytecode enhancer adds data and methods to classes to enable their instances to be managed by a persistence engine. A bytecode enhancer reads a class file and enhances it, outputting a class file with the necessary persistence functionality.

The JDO reference-implementation’s bytecode enhancer modifies persistence-capable classes to run in the JDO environment. It accomplishes this by making changes to the class’s bytecode. This enables the state of an instance of the class to be synchronized with data in a datastore.

Here is an example of running the JDO reference-implementation’s bytecode enhancer for the sample application:

java -classpath $JDOHOME/jdo.jar:$JDOHOME/jdori.jar:$JDOHOME/jdori-enhancer.jar: 
com.sun.jdori.enhancer.Main -d ../enhanced -s . -f ./com/jeffhanson/businesstier/model/package.jdo
./com/jeffhanson/businesstier/model/UserInfo.class

The JDO Query Language
JDO defines a query language known as JDOQL. This language is embodied within the javax.jdo.Query interface. The JDO PersistenceManager defines methods for constructing instances of Query-instance implementation classes, as follows:

  • Query newQuery();
  • Query newQuery(Class cls);
  • Query newQuery(Class cls, Collection coll);
  • Query newQuery(Class cls, String filter);
  • Query newQuery(Class cls, Collection c, String filter);
  • Query newQuery(Extent ext);
  • Query newQuery(Extent ext, String filter);

A query filter is specified as a boolean expression that loosely resembles the boolean operators of SQL. The result for a query depends on whether or not the boolean expression evaluates to true for the specified candidate classes. The following method is provided by the Query interface for specifying a filter:

  • void setFilter(String filter);

Candidate classes can be specified using one of the following methods of the Query interface:

  • void setClass(Class candidateClass);
  • void setCandidates(Collection candidates);
  • void setCandidates(Extent candidates);

The Query interface provides methods to execute a query. Query parameters can be passed directly to any of these methods:

  • Object execute(Object param);
  • Object execute(Object param1, Object param2);
  • Object execute(Object param1, Object param2, Object param3);
  • Object executeWithMap(Map paramMap);
  • Object executeWithArray(Object[] paramArray);

The Query interface also provides a method for declaring an arbitrary group of comma-separated parameters:

  • void declareParameters(String paramStr);

This group of parameters must be formatted as a string defining Java-type and parameter-name pairs. For example, the following snippet illustrates a parameter group for a user’s name and a user’s account number:

query.declareParameters("String userName, int accountNumber");

The following illustrates a complete query using JDOQL:

String filter = "userInfo.fullName == fullName && "              + "userInfo.zip.startsWith(userZip)";Extent extent = persistenceMgr.getExtent(UserInfo.class, true);Query query = persistenceMgr.newQuery(extent, filter);query.declareParameters("String city, String state");Collection result = (Collection)query.execute("Shire", "New York");Iterator iter = result.iterator();while(iter.hasNext()){   UserInfo user = (UserInfo)iter.next();   System.out.println("User: " + user.getID());}query.close(result);

The structural differences between Java object hierarchies and relational database tables makes the challenge of persisting Java object data to and from relational databases quite intimidating for developers. The “impedance mismatch” between relational tables and Java object hierarchies has led to the development of several different object-persistence technologies attempting to bring together the relational world and the object-oriented world. JDO defines an object/relational framework and query language that ease the challenge of storage and retrieval of Java objects to and from a datastore.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: