An Introduction To Java Data Objects

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.

VendorProduct
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.

  1. public abstract javax.jdo.PersistenceManager jdoGetPersistenceManager();
  2. public abstract void jdoReplaceStateManager(javax.jdo.spi.StateManager)
  3. throws SecurityException;
  4. public abstract void jdoProvideField(int);
  5. public abstract void jdoProvideFields(int[]);
  6. public abstract void jdoReplaceField(int);
  7. public abstract void jdoReplaceFields(int[]);
  8. public abstract void jdoReplaceFlags();
  9. public abstract void jdoCopyFields(Object, int[]);
  10. public abstract void jdoMakeDirty(String);
  11. public abstract Object jdoGetObjectId();
  12. public abstract Object jdoGetTransactionalObjectId();
  13. public abstract boolean jdoIsDirty();
  14. public abstract boolean jdoIsTransactional();
  15. public abstract boolean jdoIsPersistent();
  16. public abstract boolean jdoIsNew();
  17. public abstract boolean jdoIsDeleted();
  18. public abstract javax.jdo.spi.PersistenceCapable
  19. jdoNewInstance(javax.jdo.spi.StateManager);
  20. public abstract javax.jdo.spi.PersistenceCapable
  21. jdoNewInstance(javax.jdo.spi.StateManager, Object);
  22. public abstract Object jdoNewObjectIdInstance();
  23. public abstract Object jdoNewObjectIdInstance(String);
  24. public abstract void jdoCopyKeyFieldsToObjectId(Object);
  25. public abstract void jdoCopyKeyFieldsToObjectId(
  26. javax.jdo.spi.PersistenceCapable.ObjectIdFieldSupplier, Object);
  27. public abstract void jdoCopyKeyFieldsFromObjectId(
  28. 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.

  1. public PersistenceManager getPersistenceManager()
  2. public PersistenceManager getPersistenceManager(String userid,
  3. 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.

  1. // Instantiate SolarMetric's implementation of the
  2. // PersistenceManagerFactory interface...
  3. PersistenceManagerFactory managerFactory =
  4. new com.solarmetric.kodo.impl.jdbc.JDBCPersistenceManagerFactory();
  5.  
  6. // retrieve a manager...
  7. 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.

  1. public abstract void makePersistent(Object);
  2. public abstract void makePersistentAll(Object[]);
  3. public abstract void makePersistentAll(java.util.Collection);

Passing JDO Instance objects to any of these methods adds those objects to the data store.

  1. // retrieve a manager...
  2. PersistenceManager manager = managerFactory.getPersistenceManager();
  3.  
  4. // the Employee class must implement PersistenceCapable...
  5. Employee newEmployee = new Employee(...);
  6. 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.

  1. // retrieve a manager...
  2. PersistenceManager manager = managerFactory.getPersistenceManager();
  3.  
  4. // the Employee class must implement PersistenceCapable...
  5. Extent employeesExtent = manager.getExtent(Employee.class, false);
  6. 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.

  1. // retrieve a manager...
  2. PersistenceManager manager = managerFactory.getPersistenceManager();
  3.  
  4. // the Employee class must implement PersistenceCapable...
  5. Extent employeesExtent = manager.getExtent(Employee.class, false);
  6.  
  7. // a Query to retrieve all of the Employees who have been
  8. // with the company for more than 5 years...
  9. Query query = manager.newQuery(Employee.class, employeesExtent,
  10. "yearsOfEmployement > 5");
  11.  
  12. // execute the Query...
  13. Collection employees = (Collection) query.execute();
  14.  
  15. // process the results...
  16. Iterator iterator = employees.iterator();
  17. while (iterator.hasNext()) {
  18. Employee employee = (Employee) iterator.next();
  19. (...)
  20. }

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 implement PersistenceCapable and the vendor-specific PersistenceManagerFactory 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 Vehicles defined are Bicycle and MotorVehicle.

MotorVehicle objects have an Engine attribute.

Engine Attribute

Domain Object Source Code

  1. /**
  2.  * Fleet.java
  3.  */
  4.  
  5. package com.ociweb.jdodemo;
  6.  
  7. import java.util.Iterator;
  8. import java.util.List;
  9. import java.util.Vector;
  10.  
  11. public class Fleet {
  12.  
  13. private List vehicles = new Vector();
  14.  
  15. public void addVehicle(Vehicle vehicle) {
  16. vehicles.add(vehicle);
  17. }
  18.  
  19. public Iterator getVehicles() {
  20. return vehicles.iterator();
  21. }
  22.  
  23. public String toString() {
  24. StringBuffer buffer = new StringBuffer("Fleet:\n");
  25. Iterator iter = getVehicles();
  26. while (iter.hasNext()) {
  27. buffer.append("\t" + iter.next() + "\n");
  28. }
  29. return buffer.toString();
  30. }
  31. }
  32. /**
  33.  * Vehicle.java
  34.  */
  35. package com.ociweb.jdodemo;
  36.  
  37. public class Vehicle {
  38.  
  39. private int numberOfWheels;
  40.  
  41. public Vehicle(int numberOfWheels) {
  42. this.numberOfWheels = numberOfWheels;
  43. }
  44.  
  45. public int getNumberOfWheels() {
  46. return numberOfWheels;
  47. }
  48. }
  49.  
  50. /**
  51.  * Bicycle.java
  52.  */
  53. package com.ociweb.jdodemo;
  54.  
  55. public class Bicycle extends Vehicle {
  56.  
  57. private String model;
  58.  
  59. public Bicycle(String model) {
  60. super(2);
  61. this.model = model;
  62. }
  63.  
  64. public String toString() {
  65. return "Bike: Model " + model;
  66. }
  67. }
  68.  
  69. /**
  70.  * MotorVehicle.java
  71.  */
  72. package com.ociweb.jdodemo;
  73.  
  74. public class MotorVehicle extends Vehicle {
  75.  
  76. private Engine engine;
  77.  
  78. public MotorVehicle(int numberOfWheels, Engine engine) {
  79. super(numberOfWheels);
  80. this.engine = engine;
  81. }
  82.  
  83. public String toString() {
  84. return "MotorVehicle With " + getNumberOfWheels()
  85. + " Wheels. " + engine;
  86. }
  87. }
  88.  
  89. /**
  90.  * Engine.java
  91.  */
  92. package com.ociweb.jdodemo;
  93.  
  94. public class Engine {
  95.  
  96. private int numberOfCylinders;
  97.  
  98. public Engine(int numberOfCylinders) {
  99. this.numberOfCylinders = numberOfCylinders;
  100. }
  101.  
  102. public int getNumberOfCylinders() {
  103. return numberOfCylinders;
  104. }
  105.  
  106. public String toString() {
  107. return numberOfCylinders + " Cylinder Engine.";
  108. }
  109. }

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:

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.

  1. <?xml version="1.0"?>
  2. <jdo>
  3. <package name="com.ociweb.jdodemo">
  4. <class name="Engine"></class>
  5. <class name="Vehicle"></class>
  6. <class name="Bicycle" persistence-capable-superclass="Vehicle"></class>
  7. <class name="MotorVehicle" persistence-capable-superclass="Vehicle"></class>
  8. <class name="Fleet">
  9. <field name="vehicles">
  10. <collection element-type="Vehicle"/>
  11. </field>
  12. </class>
  13. </package>
  14. </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.

  1. /**
  2.  * SeedDatabase.java
  3.  */
  4. package com.ociweb.jdodemo;
  5.  
  6. // vendors implementation of the PersistenceManagerFactory
  7. import com.solarmetric.kodo.impl.jdbc.JDBCPersistenceManagerFactory;
  8.  
  9. import javax.jdo.PersistenceManager;
  10. import javax.jdo.Transaction;
  11.  
  12. public class SeedDatabase {
  13.  
  14. public static void main(String[] args) {
  15. // create a fleet of vehicles...
  16. Fleet fleet = new Fleet();
  17. fleet.addVehicle(new Bicycle("Schwinn"));
  18. fleet.addVehicle(new Bicycle("Giant"));
  19. fleet.addVehicle(new MotorVehicle(4, new Engine(8)));
  20. fleet.addVehicle(new MotorVehicle(2, new Engine(4)));
  21. fleet.addVehicle(new MotorVehicle(4, new Engine(4)));
  22.  
  23. // get the PersistenceManager...
  24. PersistenceManager pm =
  25. new JDBCPersistenceManagerFactory().getPersistenceManager();
  26.  
  27. // begin a transaction...
  28. Transaction transaction = pm.currentTransaction();
  29. transaction.begin();
  30.  
  31. // persist the fleet...
  32. pm.makePersistent(fleet);
  33.  
  34. // commit the transaction...
  35. transaction.commit();
  36.  
  37. // close the manager...
  38. pm.close();
  39. }
  40. }

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.

  1. /**
  2.  * ListAll.java
  3.  */
  4. package com.ociweb.jdodemo;
  5.  
  6. // vendors implementation of the PersistenceManagerFactory
  7.  
  8. import com.solarmetric.kodo.impl.jdbc.JDBCPersistenceManagerFactory;
  9.  
  10. import javax.jdo.Extent;
  11. import javax.jdo.PersistenceManager;
  12. import javax.jdo.PersistenceManagerFactory;
  13. import javax.jdo.Query;
  14. import java.util.Collection;
  15. import java.util.Iterator;
  16.  
  17. public class ListAll {
  18.  
  19. public static void main(String[] args) {
  20.  
  21. // vendor specific factory implementation...
  22. PersistenceManagerFactory managerFactory =
  23. new JDBCPersistenceManagerFactory();
  24.  
  25. // retrieve a manager...
  26. PersistenceManager manager =
  27. managerFactory.getPersistenceManager();
  28.  
  29. Extent ext = manager.getExtent(Vehicle.class, true);
  30. Query query = manager.newQuery(Vehicle.class, ext, "");
  31. Collection vehicles = (Collection) query.execute();
  32. Iterator iterator = vehicles.iterator();
  33. while (iterator.hasNext()) {
  34. Vehicle vehicle = (Vehicle) iterator.next();
  35. System.out.println("vehicle = " + vehicle);
  36. }
  37. manager.close();
  38. }
  39. }

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.

  1. /**
  2.  * ListFourCylinderVehicles
  3.  */
  4. package com.ociweb.jdodemo;
  5.  
  6. // vendors implementation of the PersistenceManagerFactory
  7. import com.solarmetric.kodo.impl.jdbc.JDBCPersistenceManagerFactory;
  8.  
  9. import javax.jdo.Extent;
  10. import javax.jdo.PersistenceManager;
  11. import javax.jdo.PersistenceManagerFactory;
  12. import javax.jdo.Query;
  13. import java.util.Collection;
  14. import java.util.Iterator;
  15.  
  16. public class ListFourCylinderVehicles {
  17.  
  18. public static void main(String[] args) {
  19.  
  20. // vendor specific factory implementation...
  21. PersistenceManagerFactory managerFactory =
  22. new JDBCPersistenceManagerFactory();
  23.  
  24. // retrieve a manager...
  25. PersistenceManager manager =
  26. managerFactory.getPersistenceManager();
  27.  
  28. Extent ext = manager.getExtent(MotorVehicle.class, true);
  29.  
  30. // only retrieve vehicles with 4 cylinders...
  31. Query query = manager.newQuery(MotorVehicle.class, ext,
  32. "engine.numberOfCylinders == 4");
  33.  
  34. Collection vehicles = (Collection) query.execute();
  35. Iterator iterator = vehicles.iterator();
  36. while (iterator.hasNext()) {
  37. Vehicle vehicle = (Vehicle) iterator.next();
  38. System.out.println("vehicle = " + vehicle);
  39. }
  40. manager.close();
  41. }
  42. }

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



Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret