Technical Overview

The Java™ Object Persistence (JOP) provides an easy to use mechanism for saving and retrieving objects. To save an object a developer needs to do two things. First open a persist manager somewhere in the mainline of your program:

    String  driver = "jdbc.odbc.JdbcOdbcDriver" ;
    String  url = "jdbc:odbc:mydatabase" ;

    PersistManager pmgr = new JdbcPersistManager( driver,
                                                  url,
                                                  "user",
                                                  "password") ;
    pmgr.open() ;

and then use the persist manager to save your object. This will also automatically save all the objects that are referenced by your object.

    YourClass myObject = ... ;
    long objectId = pmgr.streamOutObject( myObject ) ;

The objectId is a unique identifier for your object returned by JOP. To retrieve the saved object (and all of its references) in another program or in a later session simply pass the objectId as follows:

    aNewObject = (YourClass)pmgr.streamInObject( objectId )

In the above example aNewObject will have identical content to myObject, including all the references of myObject.

JOP also supports the saving and retrieval of named objects.

JOP's internal cache ensures that aNewObject and myObject will be references to the exact same object if the above operations are performed within a unit of work.

Saving Objects

An object is saved by saving all of the 'slots' (also called attributes, properties, fields or instance variables) of the object. Simple slots are stored 'in-line' whereas slots that are references to other objects cause the object graph to be traversed. This is called persistence by reachability.

As each object in the graph is saved it is assigned a unique object identifier. The object identifier ('id') of the saved object is returned to the calling method. Within a unit of work the same object will always receive the same object 'id' even if it is saved multiple times.

Retrieving Objects

Retrieving an object is the reverse process of saving an object. The 'id' of the object is passed to the ' streamInObject()' method of the PersistManager and the full object graph is retrieved. If an object is referenced multiple times within the graph then only one instance will be created. This is further extended so that if an object is referenced multiple times within a unit of work then only one instance will be created.

Named Objects

JOP also supports the concept of well known named or 'root' objects. This allows objects to be saved with a specific name and retrieved using the name in a later session. To save a named object you would do the following:

    pmgr.streamOutRoot( "myobject", myObject ) ;
    
The object (and all of the objects that it references) can be retrieved in a later session by reversing this process:

    aRootObject = (YourClass)pmgr.streamInRoot( "myobject" )
    
JOP uses the HashtableDB class to provide this facility.

Marshalling

In order for an object to be saved to persistent storage JOP needs to access the slots (also called fields or instance variables) of the object. This process of accessing the slots and packaging slot values for storage is termed marshalling.

JOP supports three different marshalling mechanisms and automatically chooses one of these mechanisms. The three mechanisms (in order of selection by JOP) are:

  1. The object implements the Persistable interface.
  2. A custom marshalling class has been provided. The custom marshaller is detected automatically by JOP.
  3. The object is automatically marshalled by JOP.

Note that in JOP will resort to automatic marshalling for any object that does not implement Persistable or have a custom marshall class. Until JDK 1.1 is released automatic marshalling requires the use of native methods.

Persistable Interface

For maximum control over the how an object is to be saved or read it should implement the JOP.persist.Persistable interface, the object's class should be public and the object should have a public constructor that takes no arguments. The Persistable interface defines three methods that must be implemented:

An object that must not be stored can implement Persistable and throw an Exception from within the defineObject() method.

Custom Marshalling

Normally JOP can automatically marshall any object using native methods. However, there are circumstances where a custom marshalling solution is more appropriate. These circumstances where you want to control the persistence operation and cannot modify the source code to the object to implement Persistable (eg java.util.Vector).

Custom marshalling is achieved by providing a class that implements the JOP.persist.PersistMarshall interface. JOP requires that custom marshall classes have a name that ends in _JOP and, the custom marshall class must be in a package where the package name is a combination of 'JOP.persistmarshall' plus the package name of the class being marshalled. For example, the custom marshall class for XYZ.abc.def.Yourclass would look like this:

    package JOP.persistmarshall.XYZ.abc.def ;

    public class Yourclass_JOP implements JOP.persist.PersistMarshall
    {
        public Yourclass_JOP()
        {
        ...
        }
    ...
    }
    

Implementation of custom marshall classes is not complicated. Examples of marshalling of java.util.Vector and java.lang.String are included with JOP.

Automatic Marshalling

JOP will automatically marshall any object that does not implement Persistable and that does not have a custom marshall class. The automatic marshalling facility requires the use of native methods. There are security and portability constraints when using native methods with Java. No involvement from the user of JOP is required to use automatic marshalling.

All slots except slots declared as 'static' or 'transient' are saved. Some of Java's internal classes eg 'java.lang.Class' are not true classes and cannot be automatically marshalled.

Units of Work

The PersistManager supports the concept of a unit of work. There are four methods to support units of work: beginWork(), commitWork(), abortWork() and transId(). The primary purpose of a unit of work is to support the maintenance of object identity. Within a unit of work a single object will always remain the same object in memory and on the database. In practical terms what this means is that the object graph in memory and on the database will be identical AND when the graph is read back in a later session it will be recreated exactly. If an object is referenced in several object graphs within a unit of work then only a single instance of that object will appear in memory or on the database. Units of work are also related to transactions.

Transactions

Some PersistManager implementations support database transactions. These coincide with the unit of work. Therefore not only is identity of objects within a unit of work preserved, but if the PersistManager supports transactions then all object graphs saved during the unit of work will be committed or rolled-back as a unit. The JdbcPersistManager supports transactions. Of course, transaction support is not available if the underlying data manager does not support transactions.

Cache

Within a unit of work the PersistManager implements an object cache. Objects are only read from the file or relational database if they are not in the cache. Objects are only written to the file or the relational database if they have changed. Detection of object changes is accomplished by calculating a 32bit checksum of the objects slots.

Improving Efficiency - Late Fetching Objects

Sometime in an application very large graphs of objects exist. In order to improve efficiency there are three classes that support the late fetching of objects. These classes are JOP.util.RefDB, JOP.util.HashtableDB and JOP.util.VectorDB. RefDB allows a single object reference to be resolved 'just in time', reading in the referenced object only when it is required. The HashtableDB and VectorDB are similar to java.util.Hashtable and java.util.Vector and offer similar external behaviour. There are some minor differences (eg HashtableDB does not allow duplicate, identical keys). Both implementations only support a subset of their Java namesakes.

Object Identifiers

JOP assigns an object identifier to each saved object. The object identifier is a 64bit long number. Object identifiers are assigned by using an algorithm that minimises the probability of two objects being assigned the same identifier. The algorithm is similar to the DCE UUID generator and uses the network address, current time and a sequene number.

JOP reserves a range of object identifiers that can be pre-defined by your application. These reserved object identifiers can be assigned to 'well-defined' objects as an alternate to using named objects. Typically objects stored with pre-defined identifiers will be HashtableDB instances.

Utilities

To help you diagnose and support the use of JOP several of the test utilities double as useful diagnostic aids.

For examples of how to use the various JOP classes the Test programs in the JOP/test directory may offer some insight.

Last updated: 15-Sep-1996
Copyright © 1996, David Rothwell All rights reserved.
Send comments to
David Rothwell