|
|
|
|
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.
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 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.
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 |
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.
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, which 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 which 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();
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);
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();
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.
The following samples demonstrate some basic uses of JDO. The code
demonstrates how to populate and retrieve data from the data store.
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 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.
/**
* 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.";
}
}
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.
<?xml version="1.0"?>
<jdo>
<package name="com.ociweb.jdodemo">
<class name="Engine"/>
<class name="Vehicle"/>
<class name="Bicycle" persistence-capable-superclass="Vehicle"/>
<class name="MotorVehicle" persistence-capable-superclass="Vehicle"/>
<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.
With the database configured and our domain objects designed, coded and
bytecode enhanced to be PersistenceCapable, now instances of
those classes may 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();
}
}
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.
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.
Object Computing, Inc (OCI) has been providing educational services to clients, industries and universities since 1993. We offer one of the most comprehensive distributed Object Oriented training curricula in the country. These curricula focus on the fundamentals of OO technology; with close to 40 workshops in OOAD, Java, XML, C++/CORBA and Unix/Linux.
For further information regarding OCI's Educational Services programs, please visit our Educational Services section on the web or contact us at training.
|
|
|