ObjectDB ObjectDB

[ODB1] Chapter 3 - Persistent Classes

Persistent Classes are user defined classes whose instances can be stored in a database using JDO. Instances of these classes that represent objects in the database are called persistent objects or persistent instances. Objects that do not represent anything in the database (including instances of persistent classes that live only in memory) are called transient objects or transient instances. The JDO specification refers to persistent classes as persistence capable classes.

3.1  Persistent Classes

Only classes that represent data in the database should be declared persistent because persistent classes have some overhead. Classes that are not declared persistent are called transient classes and can have only transient objects as instances.

To become persistent, a class has to:

  • be declared in a JDO metadata file in XML format.
  • include a no-arg constructor.
  • implement the javax.jdo.spi.PersistenceCapable interface.

The PersistenceCapable interface includes more than 20 abstract methods, so implementing it explicitly is not trivial. It is easier to define a class without implementing the PersistenceCapable interface, and have the JDO Enhancer add the interface implementation automatically, as explained in section 3.3. ObjectDB's JDO Enhancer also adds a no-arg constructor if it is missing, so out of the three requirements described above, developers only have to provide the JDO metadata declaration. Chapter 4 explains how to write the JDO metadata declaration.

Aside from the requirements described above, a persistent class is like any other Java class. It can include constructors, methods, fields, attributes (final, abstract, public), inheritance (even from a non persistent class), interface implementations, inner classes, etc. The class members (constructors, methods and fields) can have any access modifiers (i.e. public, protected, package or private).

3.2  Persistent Fields and Types

Storing a persistent object in the database does not store methods and code. Only the state of the object as reflected by its persistent fields is stored. Persistent fields, by default, are all the fields that are not defined as static, final or transient, and have persistent types. Every persistent class is a persistent type. The following predefined system types are also persistent types:

  • All the primitive types - boolean, byte, short, char, int, long, float and double.
  • Selected classes in package java.lang:
    Boolean, Byte, Short, Character, Integer, Long, Float, Double, Number and String.
  • Selected classes in package java.util:
    Date, Locale, HashSet, TreeSet, ArrayList, LinkedList, Vector, HashMap, TreeMap and Hashtable, and the interfaces - Collection, Set, List and Map.
  • java.math.BigInteger and java.math.BigDecimal
  • Any array of persistent type, including multi dimensional arrays

Some of the persistent types above are defined as optional by JDO (arrays and most of the collection classes), but ObjectDB supports all of them. You cannot extend the list to include other system types. The only way to add support for additional types is to define new persistent classes. For example, the class java.awt.Image is not supported by JDO. You can store images in byte[] fields or you can define a new persistent class for this purpose (images can also be stored as external files, storing only the file names or paths in the database).

Static and final fields can never be persistent. The transient modifier can be used to exclude other fields from being stored in the database. It is recommended to explicitly define fields of non persistent types as transient, even though they are considered transient in JDO by default. A field with a transient modifier can still become persistent by an explicit instruction in the JDO metadata (useful when a field has to be transient in serialization but persistent in JDO)

Of course, persistent fields should not hold non persistent values at runtime when the object is stored in the database. For example, a field whose type is Number (which is an abstract class) must have a reference to an object whose type is persistent at storage time, e.g. an Integer, or null value. Similarly, a field whose type is one of the persistent collection interfaces (Collection, Set, List and Map) cannot refer to an unsupported collection, or a collection that contains objects of non persistent types, at storage time.

As an extension to JDO, ObjectDB supports storing instances of any of the persistent types defined above in the database (as demonstrated in section 2.1 for ArrayList). Portable JDO applications, however, should only store instances of user defined persistent classes in the database directly and use the other persistent types only as fields in these classes.

3.3  JDO Enhancement

The ObjectDB JDO Enhancer is a post compilation tool that modifies the byte code of compiled classes. Classes to be enhanced must be located in class files and not in jar files. However, enhanced classes can be packed in jar files.

Two types of classes must be enhanced:

Persistence Capable Classes

Incomplete persistent classes that do not implement the PersistenceCapable interface explicitly (as is usually the case) must be enhanced. The enhancer modifies the byte code of these classes, and the result is classes that do implement the PersistenceCapable interface. The JDO specification refers to these classes as persistence capable classes.

Persistence Aware Classes

Classes that access or modify persistent fields are referred to as persistence aware classes. Most persistent classes are also persistence aware, according to this definition, but usually the term 'persistence aware' refers to classes that are not persistent but contain code that accesses or modifies persistent fields of other classes. Directly accessing a single persistent field is sufficient for a class to become persistence aware. Accessing a persistent field indirectly by calling a method or by getting its value as an argument to a method, on the other hand, does not make the class persistence aware. One exception, however, is in working with persistent array fields (persistent fields of types like int[] or Object[]). Any class in which a persistent array is modified, no matter how the array is accessed, is considered a persistence aware class by ObjectDB. This exception only applies to persistent arrays and not to persistent collections (such as ArrayList).

Persistence aware classes must be enhanced in order to track persistent fields. ObjectDB must know when a persistent field is modified during a transaction because the change must be applied to the database when the transaction is committed. ObjectDB must also know when a persistent field is accessed because it might require loading additional data and objects from the database (as part of transparent persistence support). Enhancement replaces direct persistent field accesses with equivalent methods that perform the same accesses but also report to ObjectDB. Tracking changes in persistent arrays (in enhanced classes), which is optional in JDO, is also fully supported by ObjectDB.

Persistence aware classes that are not persistent themselves are supported by ObjectDB and JDO, but it might be a good practice to avoid them. This can be achieved by defining all persistent fields as private, or by accessing the fields directly only in persistent classes. Because persistent classes are automatically enhanced also as persistence aware, working with persistent fields only in persistent classes simplifies things.

Command Line Enhancement

The classic method to enhance classes is to run the enhancer from the command line.

First, the classpath has to be set to include ObjectDB and JDO jar files. For example:

On Windows command:
  > set classpath=%classpath%;.;c:\objectdb\lib\odbfe.jar;c:\objectdb\lib\jdo.jar
  
On Unix sh / bash shell:
  % CLASSPATH=${CLASSPATH}:.:/objectdb/lib/odbfe.jar:/objectdb/lib/jdo.jar
On Unix csh shell: 
  % setenv CLASSPATH ${CLASSPATH}:.:/objectdb/lib/odbfe.jar:/objectdb/lib/jdo.jar

The enhancer can be run without arguments to get usage instructions:

> java com.objectdb.Enhancer
ObjectDB JDO Enhancer - Version 1.00
Copyright (C) ObjectDB Software 2003. All rights reserved.

Usage: java com.objectdb.Enhancer [ <options> | <class> | <filename> ] ...

<class> - class name (without .class suffix) found in the CLASSPATH
<filename> - absolute or relative file specification (including *? wildcards)

<options> include:
  -cp        classpath for input user classes
  -d         output path for enhanced classes
  -s         include sub directories in search
  -noopt     disable enhancement optimization

You can specify class files for enhancement explicitly or by using wildcards. The output message tells how many persistence capable and persistence aware classes have been enhanced (classes that are already enhanced are excluded). The JDO metadata is expected to be found automatically. If it is not found, classes are enhanced as persistence aware but not as persistence capable, so always notice the output message that distinguishes persistence capable classes from persistence aware classes that are not persistent.

> java com.objectdb.Enhancer test/*.class test/pc/*.class
[ObjectDB Enhancer] 2 new persistence capable classes have been enhanced.
[ObjectDB Enhancer] 1 new persistence aware class has been enhanced.

Class files can also be searched automatically in sub directories using the -s option (the "*.class" expression is in quote marks here to avoid auto resolving at the shell level in some environments):

> java com.objectdb.Enhancer -s "*.class"

Use the -d option to redirect the output classes to a different directory, keeping the original class files unchanged:

> java com.objectdb.Enhancer -s "*.class" -d enhanced

Instead of specifying class files, you can specify class names (e.g. test.X) and packages (e.g. test.pc.*):

> java com.objectdb.Enhancer test.X test.pc.*

Use the -cp option to specify an alternative classpath in which to look for the classes (the default is the classpath in which the enhancer is running):

> java com.objectdb.Enhancer -cp src test.X test.pc.*

On the Fly JDO Enhancement

Command line enhancement is useful for testing and for build scripts (like ANT for example), but in most Java IDEs a plugin is required to integrate a JDO enhancer into the IDE Build command. A simple alternative that does not require a plugin and works on any Java IDE (and also from the command line) is to add a new simple main class to a project that applies on the fly enhancement:

package test;

/** Additional main - On the Fly JDO Enhancer */
public class eMain {
    public static void main(String[] args) {

     // Always start by calling the enhancer:
        com.objectdb.Enhancer.enhance("test.pc.*,test.X");
        
        // Now move to the original entry point:
        RealMain.main(args);
    }
}

The eMain class ("enhancer Main") becomes the new entry point at development time. First the enhancer is called, and when enhancement is completed, the original entry point is called. The overhead to run the enhancer on every run is negligible because the ObjectDB Enhancer is very fast. Notice that persistent classes should not be referenced in eMain. Otherwise they might be loaded by the JVM too early, before enhancement is completed.

The com.objectdb.Enhancer.enhance(...) static method accepts as an argument a string specifying the classes to be enhanced. The syntax is adopted from the import statement. You can specify full class names (e.g. test.X) as well as entire packages (e.g. test.pc.*), where elements are separated by commas.

3.4  InstanceCallbacks

The javax.jdo.InstanceCallbacks interface represents events in a persistent object's lifecycle. By implementing the InstanceCallbacks interface, a persistent class can handle these events.

Four methods are defined in the InstanceCallbacks interface:

  • void jdoPostLoad()
    Called when a persistent object is being loaded from the database, just after the default fetch group fields (see Chapter 4) are initialized. A possible action is to initialize non persistent fields of the object. Other persistent objects, and even persistent fields in this loaded object that are not in the default fetch group, should not be accessed by this method.
  • void jdoPreStore()
    Called when a persistent object is going to be stored in the database during transaction commit. A possible action is to apply last minute changes to persistent fields in the object just before the store.
  • void jdoPreClear()
    Called when the persistent fields are going to be cleared at transaction end (for example on rollback). This event is rarely used. Other persistent objects and even persistent fields in this loaded object that are not in the default fetch group should not be accessed by this method.
  • void jdoPreDelete()
    Called during the execution of deletePersistent(...), just before an object is deleted. A possible action is to delete depended persistent objects as demonstrated below.

Because the four events are defined in the same interface, to implement one of them you have to implement the entire interface with all the four methods, keeping the methods that are irrelevant empty. For example:

package test; 
import javax.jdo.*;

class Line implements InstanceCallbacks {
  private Point p1, p2;
  public Line(Point p1, Point p2) {
    this.p1 = p1;
    this.p2 = p2;
  }
  public void jdoPostLoad() {
  }
  public void jdoPreClear() {
  }
  public void jdoPreStore() {
  }
  public void jdoPreDelete() {
    PersistenceManager pm = JDOHelper.getPersistenceManager(this); 
    pm.deletePersistent(p1);
    pm.deletePersistent(p2);
  }
}

When a Line instance is deleted, the two referred Point instances are deleted with it.

3.5  Automatic Schema Evolution

Most of the changes to persistent classes do not affect the database. This includes adding, removing and changing constructors, methods and non persistent fields. Changes to persistent fields (schema changes), however, do affect the database. New persistent objects are stored using the new schema (the new class structure), and old persistent instances, which were stored using the old schema, have to be converted to the new schema. ObjectDB implements an automatic schema evolution mechanism, which enables transparent use of old schema instances. When an old instance is loaded into the memory it is automatically converted into an instance of the new up-to-date persistent class.

The conversion is straightforward. New persistent fields that are missing in the old schema are initialized with default values (0, false or null). Old persistent fields that are missing in the new class are just ignored. When a type of a persistent field is changed, if casting of the old value to the new type is valid in Java (for example from int to float and from float to int) the old value is converted automatically to the new type. If casting is illegal (for example from int to Date) the field is initialized with a default value as a new field.

When an upgraded object is stored again in the database, it is stored using the new schema. Until then, the conversion is done only in memory each time the object is loaded, and the content of the object in the database remains in its old schema without any change.