An Introduction To Java Data Objects
By Jeff Brown, OCI Senior Software Engineer
June 2002
Introduction
Java Data Objects (JDO) is a specification to enable transparent persistence of Java objects.
The JDO specification exists as Java Specification Request 12 (JSR 12) from the Java Community Process (JCP). The first version of the specification made available for public review was posted on July 6, 2000, and version 1.0 of the specification was posted on April 30, 2002.
The two primary goals of the specification are to provide an API for transparent data access and to allow implementations of the specification to be plugged into application servers.
JDO and JDBC
Java Database Connectivity (JDBC) and JDO are APIs for accessing data from Java. To different extents, each provides a level of abstraction away from the details of the data store.
JDBC does a good job of insulating application code from details, such as the database location and vendor. In most cases the data store is going to be a relational database.
While JDBC drivers may be implemented to access non-relational databases, this is not the norm and is not where JDBC is best suited.
On the other hand, the data store behind a JDO implementation may be a relational database, an object oriented database, or something entirely different.
In the case where the data store behind the JDO implementation is a relational database, the JDO implementation may very well be using JDBC to access the database. But, all of this is hidden from the application/component developer and taken care of by the JDO implementation itself.
JDBC does not provide an object-oriented view of the database. JDBC's view of the database is very much centered on the relational database model. This often leads to code being written as a layer between the application and the database. This layer's responsibilities may include decomposing Java objects.
Decomposing a Java object is the process of breaking an object into is smallest pieces, so the object may be stored in a relational database. Likewise, a mechanism would have to be developed to convert rows of data retrieved from the relational database into the appropriate Java objects.
In contrast, JDO's view of the database is very much object-oriented. This approach is not new and has existed in object-oriented databases for some time.
The query language used with JDBC is almost always Structured Query Language (SQL).
The query language used by JDO looks a whole lot like Java code. Working with JDO does not require learning a language like SQL. If you know Java, then you know JDO's query language.
Implementations
The JDO specification defines the interface to a JDO implementation and defines the behavior of the implementation.
There is a reference implementation of the specification available from Sun, but as of May 2002, it does not fully implement the specification yet. There are numerous commercial implementations of the JDO specification available. Just a few are listed in the following table.
Vendor | Product |
---|---|
SolarMetric | Kodo JDO |
PrismTech | OpenFusion Java Data Objects |
Signsoft | intelliBO |
Poet | FastObjects |
The API
javax.jdo.spi.PersistenceCapable
Any class that is to be managed by a JDO implementation must implement the PersistenceCapable
interface.
An instance of any class that implements the PersistenceCapable
interface is known as a "JDO Instance." This interface defines the methods used by the JDO implementation to manage instances of this class.
- public abstract javax.jdo.PersistenceManager jdoGetPersistenceManager();
- public abstract void jdoReplaceStateManager(javax.jdo.spi.StateManager)
- throws SecurityException;
- public abstract void jdoProvideField(int);
- public abstract void jdoProvideFields(int[]);
- public abstract void jdoReplaceField(int);
- public abstract void jdoReplaceFields(int[]);
- public abstract void jdoReplaceFlags();
- public abstract void jdoCopyFields(Object, int[]);
- public abstract void jdoMakeDirty(String);
- public abstract Object jdoGetObjectId();
- public abstract Object jdoGetTransactionalObjectId();
- public abstract boolean jdoIsDirty();
- public abstract boolean jdoIsTransactional();
- public abstract boolean jdoIsPersistent();
- public abstract boolean jdoIsNew();
- public abstract boolean jdoIsDeleted();
- public abstract javax.jdo.spi.PersistenceCapable
- jdoNewInstance(javax.jdo.spi.StateManager);
- public abstract javax.jdo.spi.PersistenceCapable
- jdoNewInstance(javax.jdo.spi.StateManager, Object);
- public abstract Object jdoNewObjectIdInstance();
- public abstract Object jdoNewObjectIdInstance(String);
- public abstract void jdoCopyKeyFieldsToObjectId(Object);
- public abstract void jdoCopyKeyFieldsToObjectId(
- javax.jdo.spi.PersistenceCapable.ObjectIdFieldSupplier, Object);
- public abstract void jdoCopyKeyFieldsFromObjectId(
- javax.jdo.spi.PersistenceCapable.ObjectIdFieldConsumer, Object);
Turning otherwise normal Java classes into JDO Instance classes is usually done using a tool provided by the JDO implementation vendor. This tool may use one of a couple of different approaches.
The first approach is to use a bytecode enhancer. A JDO bytecode enhancer transforms a standard Java class file into a JDO Instance class by inserting all of the code necessary to implement the PersistenceCapable
interface.
The second approach is a code generator that parses plain Java source code and outputs a version of that source code which implements the PersistenceCapable
interface. Implementing this interface "by hand" is not practical.
javax.jdo.PersistenceManagerFactory
The PersistenceManagerFactory
interface is the mechanism used to retrieve a PersistenceManager
instance. Two factory methods exist in the interface.
- public PersistenceManager getPersistenceManager()
- public PersistenceManager getPersistenceManager(String userid,
- String password)
Because PersistenceManagerFactory
is an interface, some vendor-specific class that implements this interface must be used as a bootstrap mechanism. This should turn out to be the only vendor-specific code that a JDO application uses. Because of this, the JDO specification suggests that an application-level factory class be implemented that returns the appropriate instance of the PersistenceManagerFactory
so that implementations may be swapped out with minimal impact on application code. Only the application's factory would need to be modified in this case.
- // Instantiate SolarMetric's implementation of the
- // PersistenceManagerFactory interface...
- PersistenceManagerFactory managerFactory =
- new com.solarmetric.kodo.impl.jdbc.JDBCPersistenceManagerFactory();
-
- // retrieve a manager...
- PersistenceManager manager = managerFactory.getPersistenceManager();
javax.jdo.PersistenceManager
The PersistenceManager
interface is the primary point of contact between a Java application and the JDO implementation.
Application code uses a PersistenceManager
to retrieve Java objects from the data store and to add Java objects to the data store. The PersistenceManager
interface also serves as a factory for several other JDO components discussed below.
Several methods exist in the PersistenceManager
interface, which add JDO Instance objects to the data store.
- public abstract void makePersistent(Object);
- public abstract void makePersistentAll(Object[]);
- public abstract void makePersistentAll(java.util.Collection);
Passing JDO Instance objects to any of these methods adds those objects to the data store.
- // retrieve a manager...
- PersistenceManager manager = managerFactory.getPersistenceManager();
-
- // the Employee class must implement PersistenceCapable...
- Employee newEmployee = new Employee(...);
- manager.makePersistent(newEmployee);
javax.jdo.Extent
Extent
objects represent all of the instances of some particular class that are currently in the data store. A factory method exists in the PersistenceManager
interface for retrieving Extent
objects.
public Extent getExtent(Class persistenceCapableClass, boolean subclasses)
The Class
argument indicates the type of objects to be retrieved. The boolean argument indicates whether subclasses of the specified class should be included.
The Extent
interface defines an iterator()
method, which returns a java.util.Iterator
for iterating over all of the instances represented by the Extent
.
- // retrieve a manager...
- PersistenceManager manager = managerFactory.getPersistenceManager();
-
- // the Employee class must implement PersistenceCapable...
- Extent employeesExtent = manager.getExtent(Employee.class, false);
- java.util.Iterator iterator = employeesExtent.iterator();
javax.jdo.Query
The Query
interface allows instances to be retrieved from the data store based on some supplied criteria. Query
instances should be retrieved using one of the overloaded newQuery()
methods in the PersistenceManager
interface.
The Query
interface defines several overloaded versions of the execute()
method which execute the Query
and return matching results.
- // retrieve a manager...
- PersistenceManager manager = managerFactory.getPersistenceManager();
-
- // the Employee class must implement PersistenceCapable...
- Extent employeesExtent = manager.getExtent(Employee.class, false);
-
- // a Query to retrieve all of the Employees who have been
- // with the company for more than 5 years...
- Query query = manager.newQuery(Employee.class, employeesExtent,
- "yearsOfEmployement > 5");
-
- // execute the Query...
- Collection employees = (Collection) query.execute();
-
- // process the results...
- Iterator iterator = employees.iterator();
- while (iterator.hasNext()) {
- Employee employee = (Employee) iterator.next();
- (...)
- }
Notice the third argument to the newQuery()
method, "yearsOfEmployement > 5". This expression constrains which objects will be returned from the store.
There must be a field in the Employee
class called yearsOfEmployment
for this to work. Any of the standard Java language relational operators may be included in this boolean expression.
Some Sample Code
Overview
The following samples demonstrate some basic uses of JDO.
The code demonstrates how to populate and retrieve data from the data store.
Note: SolarMetric's Kodo JDO implemenation is used here. The mechanisms for creating the database schema, enhancing bytecode to make the domain objects implementPersistenceCapable
and the vendor-specificPersistenceManagerFactory
implementation are the only Kodo JDO specifics being used here. Everything else should work with anyone's JDO implementation.
The Domain Objects
The samples will work with a small set of classes, which represent a Fleet
of Vehicle
objects. The two specific types of Vehicle
s defined are Bicycle
and MotorVehicle
.
MotorVehicle
objects have an Engine
attribute.
- /**
- * Fleet.java
- */
-
- package com.ociweb.jdodemo;
-
- import java.util.Iterator;
- import java.util.List;
- import java.util.Vector;
-
- public class Fleet {
-
- private List vehicles = new Vector();
-
- public void addVehicle(Vehicle vehicle) {
- vehicles.add(vehicle);
- }
-
- public Iterator getVehicles() {
- return vehicles.iterator();
- }
-
- public String toString() {
- StringBuffer buffer = new StringBuffer("Fleet:\n");
- Iterator iter = getVehicles();
- while (iter.hasNext()) {
- buffer.append("\t" + iter.next() + "\n");
- }
- return buffer.toString();
- }
- }
- /**
- * Vehicle.java
- */
- package com.ociweb.jdodemo;
-
- public class Vehicle {
-
- private int numberOfWheels;
-
- public Vehicle(int numberOfWheels) {
- this.numberOfWheels = numberOfWheels;
- }
-
- public int getNumberOfWheels() {
- return numberOfWheels;
- }
- }
-
- /**
- * Bicycle.java
- */
- package com.ociweb.jdodemo;
-
- public class Bicycle extends Vehicle {
-
- private String model;
-
- public Bicycle(String model) {
- super(2);
- this.model = model;
- }
-
- public String toString() {
- return "Bike: Model " + model;
- }
- }
-
- /**
- * MotorVehicle.java
- */
- package com.ociweb.jdodemo;
-
- public class MotorVehicle extends Vehicle {
-
- private Engine engine;
-
- public MotorVehicle(int numberOfWheels, Engine engine) {
- super(numberOfWheels);
- this.engine = engine;
- }
-
- public String toString() {
- return "MotorVehicle With " + getNumberOfWheels()
- + " Wheels. " + engine;
- }
- }
-
- /**
- * Engine.java
- */
- package com.ociweb.jdodemo;
-
- public class Engine {
-
- private int numberOfCylinders;
-
- public Engine(int numberOfCylinders) {
- this.numberOfCylinders = numberOfCylinders;
- }
-
- public int getNumberOfCylinders() {
- return numberOfCylinders;
- }
-
- public String toString() {
- return numberOfCylinders + " Cylinder Engine.";
- }
- }
The Kodo JDO Specifics
Kodo JDO includes its own utility classes for generating the database schema and enhancing bytecode.
The schema generation tool is used to create a database schema that will be used to persist JDO Instances. The officially supported list of databases includes:
- DB2
- InstantDB
- SQLServer
- MySQL
- Oracle
- PostgreSQL
Other databases with JDBC drivers may be plugged in with some extra coding. See the Kodo JDO documentation for details.
Their schema generation tool relies on a package.jdo
file that must be written to define some details about JDO Instance classes.
Following is the file used for these samples. See the Kodo JDO documentation for details about the format and contents of this file.
- <?xml version="1.0"?>
- <jdo>
- <package name="com.ociweb.jdodemo">
- <class name="Engine"></class>
- <class name="Vehicle"></class>
- <class name="Bicycle" persistence-capable-superclass="Vehicle"></class>
- <class name="MotorVehicle" persistence-capable-superclass="Vehicle"></class>
- <class name="Fleet">
- <field name="vehicles">
- <collection element-type="Vehicle"/>
- </field>
- </class>
- </package>
- </jdo>
The schematool.bat
file is provided to run the schema generator. The .jdo file must be passed as an argument on the command line.
schematool.bat package.jdo
Once the schema has been generated, the class files for the domain objects must be enhanced to implement the PersistenceCapable
interface.
The jdoc.bat
file is provided to help run the bytecode enhancer. The jdoc.bat
batch file also requires the .jdo file to be passed as an argument on the command line.
jdoc.bat package.jdo
The steps shown above for schema generation and bytecode enhancement are specific to the Kodo JDO implementation and are not defined as part of the JDO specification. Other vendors may have their own proprietary steps to accomplish these steps. Refer to the vendor's documentation.
Populating The Data Store
With the database configured, our domain objects designed and coded, and bytecode enhanced to be PersistenceCapable
, instances of those classes may now be instantiated and added to the data store.
The following class will instantiate a Fleet
, populate it with several vehicles, and then persist those vehicles.
- /**
- * SeedDatabase.java
- */
- package com.ociweb.jdodemo;
-
- // vendors implementation of the PersistenceManagerFactory
- import com.solarmetric.kodo.impl.jdbc.JDBCPersistenceManagerFactory;
-
- import javax.jdo.PersistenceManager;
- import javax.jdo.Transaction;
-
- public class SeedDatabase {
-
- public static void main(String[] args) {
- // create a fleet of vehicles...
- Fleet fleet = new Fleet();
- fleet.addVehicle(new Bicycle("Schwinn"));
- fleet.addVehicle(new Bicycle("Giant"));
- fleet.addVehicle(new MotorVehicle(4, new Engine(8)));
- fleet.addVehicle(new MotorVehicle(2, new Engine(4)));
- fleet.addVehicle(new MotorVehicle(4, new Engine(4)));
-
- // get the PersistenceManager...
- PersistenceManager pm =
- new JDBCPersistenceManagerFactory().getPersistenceManager();
-
- // begin a transaction...
- Transaction transaction = pm.currentTransaction();
- transaction.begin();
-
- // persist the fleet...
- pm.makePersistent(fleet);
-
- // commit the transaction...
- transaction.commit();
-
- // close the manager...
- pm.close();
- }
- }
Retrieving Data From The Data Store
The following code retrieves all instances of the Vehicle
class (including subclasses) from the data store and prints them to standard out.
- /**
- * ListAll.java
- */
- package com.ociweb.jdodemo;
-
- // vendors implementation of the PersistenceManagerFactory
-
- import com.solarmetric.kodo.impl.jdbc.JDBCPersistenceManagerFactory;
-
- import javax.jdo.Extent;
- import javax.jdo.PersistenceManager;
- import javax.jdo.PersistenceManagerFactory;
- import javax.jdo.Query;
- import java.util.Collection;
- import java.util.Iterator;
-
- public class ListAll {
-
- public static void main(String[] args) {
-
- // vendor specific factory implementation...
- PersistenceManagerFactory managerFactory =
- new JDBCPersistenceManagerFactory();
-
- // retrieve a manager...
- PersistenceManager manager =
- managerFactory.getPersistenceManager();
-
- Extent ext = manager.getExtent(Vehicle.class, true);
- Query query = manager.newQuery(Vehicle.class, ext, "");
- Collection vehicles = (Collection) query.execute();
- Iterator iterator = vehicles.iterator();
- while (iterator.hasNext()) {
- Vehicle vehicle = (Vehicle) iterator.next();
- System.out.println("vehicle = " + vehicle);
- }
- manager.close();
- }
- }
The output of the ListAll
program:
vehicle = Bike: Model Schwinn
vehicle = Bike: Model Giant
vehicle = MotorVehicle With 4 Wheels. 8 Cylinder Engine.
vehicle = MotorVehicle With 2 Wheels. 4 Cylinder Engine.
vehicle = MotorVehicle With 4 Wheels. 4 Cylinder Engine.
To limit the results of the Query
to Vehicle
objects with 4 cylinders, criteria must be passed to the newQuery()
method. Note that in this example the Extent
and Query
use the MotorVehicle
class instead of Vehicle
since only MotorVehicle
objects have Engines
.
- /**
- * ListFourCylinderVehicles
- */
- package com.ociweb.jdodemo;
-
- // vendors implementation of the PersistenceManagerFactory
- import com.solarmetric.kodo.impl.jdbc.JDBCPersistenceManagerFactory;
-
- import javax.jdo.Extent;
- import javax.jdo.PersistenceManager;
- import javax.jdo.PersistenceManagerFactory;
- import javax.jdo.Query;
- import java.util.Collection;
- import java.util.Iterator;
-
- public class ListFourCylinderVehicles {
-
- public static void main(String[] args) {
-
- // vendor specific factory implementation...
- PersistenceManagerFactory managerFactory =
- new JDBCPersistenceManagerFactory();
-
- // retrieve a manager...
- PersistenceManager manager =
- managerFactory.getPersistenceManager();
-
- Extent ext = manager.getExtent(MotorVehicle.class, true);
-
- // only retrieve vehicles with 4 cylinders...
- Query query = manager.newQuery(MotorVehicle.class, ext,
- "engine.numberOfCylinders == 4");
-
- Collection vehicles = (Collection) query.execute();
- Iterator iterator = vehicles.iterator();
- while (iterator.hasNext()) {
- Vehicle vehicle = (Vehicle) iterator.next();
- System.out.println("vehicle = " + vehicle);
- }
- manager.close();
- }
- }
The output of the ListFourCylinderVehicles
program:
vehicle = MotorVehicle With 2 Wheels. 4 Cylinder Engine.
vehicle = MotorVehicle With 4 Wheels. 4 Cylinder Engine.
Summary
JDO provides a view of the data store that is a lot more object oriented in comparison to using JDBC. Details about the object mapping and the data store vendor are all kept hidden from the application/component developer. The steps to populate, retrieve, and manipulate the contents of the data store are simple and clean. The amount of persistence-related code developers must write is relatively small. These are some of the reasons that JDO is such a compelling technology for Java persistence.
References
- [1] Java Community Process
http://www.jcp.org/ - [2] Java Data Objects
http://access1.sun.com/jdo/ - [3] Java Data Objects Specification
http://www.jcp.org/jsr/detail/12.jsp - [4] Java Specification Requests
http://www.jcp.org/jsr/overview/index.jsp - [5] JDBC
http://java.sun.com/products/jdbc/ - [6] JDO Central
http://www.jdocentral.com/ - [7] Poet FastObjects JDO
http://www.fastobjects.com/FO_Products_FastObjectsj1_Body.html - [8] PrismTech OpenFusion JDO
http://www.prismtechnologies.com/English/Products/JDO/index.html - [9] Signsoft IntelliBO JDO
http://www.signsoft.com/en/intellibo/jdo.jsp - [10] SolarMetric Kodo JDO
http://www.solarmetric.com/Software/Kodo_JDO/
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.