PSE/PSE Pro for Java API User Guide

Manually Generating Persistence-Capable Classes

To be stored in a database, an object must be persistence-capable. This section provides information about how to explicitly define persistence-capable classes in your program without using the automated class file postprocessor supplied with PSE/PSE Pro. Object Design recommends that you use the automated postprocessor. However, you might choose this manual method if you want to

Another feature of the postprocessor is to automatically annotate a class so that it is persistence-aware. Occasionally, you might want to manually annotate a class so that it is persistence-aware.

Contents

This chapter discusses the following topics:

Explicitly Defining Persistence-Capable Classes

Additional Information About Manual Annotation

Creating and Accessing Fields in Annotations

Explicitly Defining Persistence-Capable Classes

Follow the steps below to annotate your program so that classes you define are persistence-capable.

  1. Define a class that inherits from the COM.odi.Persistent or COM.odi.util.HashPersistent class. See Choosing Whether to Inherit From Persistent or HashPersistent.

  2. In the class definition, define the required methods. See Defining Required Methods in the Class Definition.

  3. In the class definition, define accessor methods so that they make the appropriate calls to the fetch() and dirty() methods.

  4. In the same file as the persistence-capable class, define a class that inherits from the ClassInfo class. You must define one ClassInfo subclass for each persistence-capable class you define. See Defining a ClassInfo Subclass.

  5. Create an instance of the ClassInfo subclass. Only one instance of this subclass is ever needed.

  6. Call the register() method on the instance of the ClassInfo subclass. (Typically, this is in static initializer code for the Persistent subclass.)

Choosing Whether to Inherit From Persistent or HashPersistent

When you explicitly define a persistence-capable class it must inherit from COM.odi.Persistent or COM.odi.util.HashPersistent. Here is some background so that you can decide whether to have your class inherit from Persistent or HashPersistent.

Every class inherits from the Object class, which defines the hashCode() method and provides a default implementation. For a persistent object, this default implementation almost always returns a different value for the same persistent object (the object on the disk) at different times. This is because PSE/PSE Pro fetches the persistent object into different Java objects at different times (in different transactions or different invocations of Java).

This is not a problem if you never put the object into a persistent hash table or other structure that uses the hashCode() method to locate objects. If you do put them in hash tables or something similar, the hash table or other structure that relies on the hashCode() method might become corrupted when you bring the objects back from the database. There are two ways to solve this problem:

Defining Required Methods in the Class Definition

This section describes the methods that must be in your class definition to make the class persistence-capable.

initializeContents()

Define the initializeContents() method to load real values into hollow instances of your Persistent subclass. PSE/PSE Pro provides methods on the GenericObject class that retrieve each Field type. Be sure to call the correct methods for the fields in your persistent object. There is a separate method for obtaining each type of Field object. PSE/PSE Pro calls the initializeContents() method as needed. The method signature is

public void initializeContents(GenericObject genObj)
Here is an example:

public void initializeContents(GenericObject handle) {
      name = handle.getStringField(1, PCI);
      age = handle.getIntField(2, PCI);
      children = (Person[])handle.getArrayField(3, PCI);
}
If the class you are annotating has a superclass other than COM.odi.Persistent, you must also initialize superclass fields by invoking initializeContents() on the superclass.

flushContents()

Define the flushContents() method to copy values from a modified instance (active persistent object) back to the database. PSE/PSE Pro provides methods on the GenericObject class that set each Field type. Be sure to call the correct methods for the fields in your persistent object. There is a separate method for setting each type of Field object. PSE/PSE Pro calls the flushContents() method as needed. The method signature is

public void flushContents(GenericObject genObj)
Here is an example:

public void flushContents(GenericObject handle) {
      handle.setClassField(1, name, PCI);
      handle.setIntField(2, age, PCI);
      handle.setArrayField(3, children, PCI);
}
If the class you are annotating has a superclass other than COM.odi.Persistent or COM.odi.util.HashPersistent then you must also flush superclass fields by invoking flushContents() on the superclass.

clearContents()

Define the clearContents() method to reset the values of an instance to the default values. This method must set all reference fields that referred to persistent objects to null. PSE/PSE Pro calls this method as needed. The method signature is

public void clearContents()
Here is an example:

public void clearContents() {
      name = null;
      age = 0;
      children = null;
}
If the class you are annotating has a superclass other than COM.odi.Persistent or COM.odi.util.HashPersistent then you must also clear superclass fields by invoking clearContents() on the superclass.

hashCode()

Do one of the following:

      public int hashCode()

Making Object Contents Accessible

Annotate your class definition to include calls to the fetch() and dirty() methods. These calls are required for the class to be persistence-capable. Before your application tries to access the contents of any objects, it must call the

Your application calls the method and passes an object whose contents you want to access. This makes the contents of the object available.

Modify the methods that reference nonstatic fields to call the Persistent.fetch() and Persistent.dirty() methods as needed. While this step is not mandatory, it does provide a systematic way to ensure that the application calls the fetch() or dirty() method before accessing or updating object contents.

Remember that you can add some annotations and run the postprocessor to add other annotations. You might want to define the required methods and the ClassInfo subclass, but let the postprocessor insert the required fetch() and dirty() calls.

Defining a ClassInfo Subclass

In the same file as the persistence-capable class, define a class that inherits from the ClassInfo class. If you plan to use the postprocessor to insert any annotations, the name of this class must be the name of the persistence-capable class followed by ClassInfo, for example, PersonClassInfo. You must define one ClassInfo subclass for each persistence-capable class you define. In the ClassInfo subclass definition, you must include the methods described below.

create()

Define a create() method to create instances of your persistence-capable class with default field values:

public Persistent create() { return new Person(this); }
This should call a constructor, referred to as a hollow object constructor, that leaves fields in the default state. For an abstract class, the create() method can return null.

createArray()

Define a createArray() method to create an array of references to instances of your Persistent subclass. For example:

public Object createArray (int nDimensions, 
                        int sizeOfOuterDimension)
            throws BadArrayDimensionsException
This should be prepared to create arrays with as many dimensions as your application requires.

getClassDescriptor()

Define the public getClassDescriptor() method to obtain the class object for your Persistent subclass. For example:

public Class getClassDescriptor() { 
      return Class.forName("COM.odi.demo.people.Person"); }

getFields()

Define the public getFields() method to allow access to the names and types of the fields of the Persistent subclass. For example:

public Field[] getFields() { return fields; }
private static Field[] fields = {
      Field.createString("name"),
      Field.createInt("age"),
      Field.createClassArray("children", "Person", 1)
};
The definition of the getFields() method can specify create methods for fields that are not in the class definition and can omit create methods for fields that are in the class definition.

Example of a Manually Annotated Persistence-Capable Class

Here is an example of a definition of a manually-generated persistence-capable class. Three consecutive periods indicate lines from a complete program that have been left out here because they are not pertinent to creating a persistence-capable class.

package COM.odi.demo.people;
import COM.odi.*;
// Define a class that inherits from Persistent:
public
class Person extends Persistent {
      // Fields:
      String name;
      int age;
      Person children[];
      static ClassInfo myClassInfo;
...
      // Constructor:
      public Person(String name, int age, Person children[]) {
            this.name = name; this.age = age; this.children = children;
      }
      // Hollow object constructor:
      public Person(ClassInfo) { }
      // Accessor methods that have been modified to call
      // the fetch() and dirty() methods:
      public String getName() { fetch(this); return name; }
      public void setName(String name) {dirty(this); this.name = name; }
      public int getAge() { fetch(this); return age; }
      public void setAge(int age) { dirty(this); this.age = age; }
      public Person[] getChildren() { fetch(this); return children; }
      public void setChildren(Person children[]) {
            dirty(this); this.children = children;
      }
...
      // Additions required for PSE/PSE Pro:
      // Define the initializeContents() method to load real 
      // values into hollow persistent objects, which makes
      // them active persistent objects:
      public void initializeContents(GenericObject handle) {
            name = handle.getStringField(1, PCI);
            age = handle.getIntField(2, PCI);
            children = (Person[])handle.getArrayField(3, PCI);
      }
      // Define the flushContents() method to copy the 
      // content of a persistent object to the database:
      public void flushContents(GenericObject handle) {
            handle.setClassField(1, name, myClassInfo);
            handle.setIntField(2, age, myClassInfo);
            handle.setArrayField(3, children, myClassInfo);
      }
      // Define the clearContents() method to reset the values
      // of a persistent instance to the default values.
      // This method must set all reference fields that
      // referred to persistent objects to null:
      public void clearContents() {
            name = null;
            age = 0;
            children = null;
      }
      // Create an instance of the subclass of ClassInfo and
      // register that instance:
      static {myClassInfo=new PersonClassInfo();
            ClassInfo.register(myClassInfo); }
}
// Define the subclass of ClassInfo. A recommended naming 
// convention is to prefix the name of your persistence-capable
// class to "ClassInfo".
class PersonClassInfo extends ClassInfo {
      // Define a create() method to create instances of your
      // Persistent subclass with default field values. The method
      // calls the hollow object constructor and passes this,
       // which is an instance of the ClassInfo subclass:
      public Persistent create() { return new Person(this); }
      // Define a createArray() method to create an array of references to
      // instances of your Persistent subclass:
      public Object[] createArray(int nDimensions, 
                              int sizeOfOuterDimension)
             throws BadArrayDimensionsException {
                   switch (nDimensions) {
                   case 1:
                         return new Person[sizeOfOuterDimension];
                   case 2:
                         return new Person[sizeOfOuterDimension][];
                   default:
             throw new BadArrayDimensionsException(nDimensions);
            }
      }
      // Define these public methods to provide access to
      // the name of the persistence-capable class, the name of its
      // superclass, and the names of its fields.
      // The array returned by getFields() must contain the
      // fields in the order of their field numbers.
public Class getClassDescriptor() { return 
Class.forName("COM.odi.demo.people.Person"); }
      public Field[] getFields() { return fields; }
      private static Field[] fields = {
            Field.createString("name"),
            Field.createInt("age"),
            Field.createClassArray("children", "Person", 1)
      };
}
You must create a subclass of the ClassInfo class for each class that you define that you want to be persistence-capable. ClassInfo is an abstract class for managing schema information for persistence-capable classes. PSE/PSE Pro requires the schema information to manage the object.

After you perform the steps described in this section, you can store instances of your Persistent subclass in a database.

PSE/PSE Pro does not let you store final instance variables persistently. This is because it is not possible to write the initializeContents() and clearContents() methods to correctly handle final instance variables.

Additional Information About Manual Annotation

This section provides additional information about manually annotating a class to be persistence-capable.

Working with Transient-Only and Persistent-Only Fields

The definition of the ClassInfo.getFields() method returns an array of COM.odi.Field instances, one element for each field that you want to store and retrieve in a persistent object. PSE/PSE Pro does not require an exact match between each field in the Java class definition and each field array element returned by the getFields() method. Furthermore, fields listed in the getFields() return value need not directly represent fields in the class. They can represent state from which values for fields in the class are synthesized.

Transient-only fields

A persistence-capable Java class can define a field that does not appear in the list of fields returned by the ClassInfo.getFields() method. Such a field is a transient-only field. The initializeContents() method that is associated with the class can be used to initialize transient-only fields based on persistent state.

Number of fields

The number of nonstatic, nontransient declared fields in the class should generally be equal to the number of fields reported by the getFields() method, unless the flushContents() and initializeContents() methods are written to combine or split fields. For example:

class A {
      transient java.awt.Component myVisualizationComponent;
      int myValue;
            ...
      }
In this class, the myVisualizationComponent field is declared to be a transient reference to java.awt.Component. java.awt is a package containing GUI classes that do not lend themselves to being persistence-capable.

Persistent-only fields

The getFields() method can return a list of fields that does not include one or more fields that are in the Java class definition. Such a field is a persistent-only field. The flushContents() method associated with the class must initialize persistent-only fields.

Example

An example of how you might use transient-only and persistent-only fields is in the demo directory that is included in PSE/PSE Pro. In the rep example, Rectangle.a and Rectangle.b are transient-only fields, while ax, ay, bx, and by are persistent-only fields. Here is the part of the example that shows this:

package COM.odi.demo.rep;
/**
 * A Rectangle has two Points, representing its upper-left
 * and lower-right corners. However, its persistent representation
 * is formed by storing the x and y coordinates of the two points,
 * rather than the points themselves. This demonstrates the control
 * that the definer of a persistent class has over the persistent
 * representation. Note that Identity of the Point objects is not
 * preserved, since the Point objects are not persistent objects.
 */
import COM.odi.*;
public class Rectangle extends Persistent {
      Point a;
      Point b;
      ClassInfo myClassInfo;
...
      public void initializeContents(GenericObject handle) {
            a = new Point(handle.getIntField(1, myClassInfo),
                  handle.getIntField(2, myClassInfo));
            b = new Point(handle.getIntField(3, myClassInfo),
                  handle.getIntField(4, myClassInfo));
      }
      public void flushContents(GenericObject handle) {
            handle.setIntField(1, a.x, myClassInfo);
            handle.setIntField(2, a.y, myClassInfo);
            handle.setIntField(3, b.x, myClassInfo);
            handle.setIntField(4, b.y, myClassInfo);
      }
...
      }
...
}
class RectangleClassInfo extends ClassInfo
{
...
      public Field[] getFields() { return fields; }
      private static Field[] fields =
      { Field.createInt("ax"),
            Field.createInt("ay"),
            Field.createInt("bx"),
            Field.createInt("by"), };
}

Allowing Arrays of Interfaces to Be Persistence-Capable

You can usually store interfaces persistently without additional steps because interfaces do not contain fields and cannot have instances created. However, you must perform additional steps to persistently store arrays that you declare to contain interface type elements, for example, java.lang.Cloneable[].

Steps to follow

To store an array of interface type elements you must

  1. Define a subclass of the ClassInfo class. If it is convenient to do so, place the definition in the same file as the definition of the interface with which the ClassInfo subclass is associated.

  2. Create an instance of the ClassInfo subclass.

  3. Register the ClassInfo subclass instance with a call to the register() method on the ClassInfo class.

  4. In the ClassInfo subclass definition, define the same methods you define for other persistence-capable classes:

Example

Here is an example of the definition of a ClassInfo subclass that allows an array of java.lang.Cloneable to be persistence-capable. The example does not include the call to ClassInfo.register(), which must appear in an already loaded class.

class CloneableInfo extends ClassInfo
{
      /* Cannot create instances of an interface; */
      public COM.odi.Persistent create() { return null; }
      public Object[] createArray(int nDimensions,
                              int sizeOfOuterDimension)
            throws BadArrayDimensionsException {
            switch(nDimensions) {
            case 1:
                  return new Cloneable[sizeOfOuterDimension];
            default:
                  throw new BadArrayDimensionsException(nDimensions);
            }
      }
      public Class getClassDescriptor() throws
            ClassNotFoundException {
            return Class.forName("java.lang.Cloneable");
      }
      public Field[] getFields() { return null; }
}

Defining Persistence-Aware Classes

A persistence-aware class is a class whose instances

For a class to be persistence-aware, you must annotate it so that it includes calls to the fetch() and dirty() methods. The fetch() method makes the contents of a persistent object available to be read. The dirty() method makes the contents of a persistent object available to be modified.

To make a class persistence-aware, modify each method that references nonstatic fields so that it calls the Persistent.fetch() or Persistent.dirty() method. This call must be before any attempt to access the contents of the persistent object. The fetch() and dirty() methods make the contents of persistent objects available.

Following Postprocessor Conventions

If you plan to explicitly define all required annotations, you need not be concerned with postprocessor conventions. However, if you plan to explicitly insert some annotations and use the postprocessor to insert other annotations, you must follow these postprocessor conventions.

Annotating Abstract Classes

Persistence-capable classes and their superclasses, even if they are abstract, must each have a corresponding ClassInfo subclass. But an application does not create instances of abstract classes, so you cannot write the required create() method in the ClassInfo subclass in the usual way. Define the create() method so that it returns null. Since this method will never be called, it is safe to define it this way.

Now, suppose you define the following two classes:

abstract class Y {
      int yValue;
      abstract void doSomething();
}
class X extends Y {
      float xValue;
      void doSomething() {}
}
Class Y must have an associated ClassInfo subclass and class X must have an associated ClassInfo subclass. Both subclasses extend ClassInfo. The ClassInfo subclass associated with X does not extend the ClassInfo subclass associated with Y.

Similarly, in the ClassInfo subclass for X, the Field array must include only those fields defined explicitly in X; XClassInfo.getFields() must report only the immediate persistent fields in X. The ClassInfo subclass for Y defines a Field array that contains the fields explicitly defined in Y.

Creating and Accessing Fields in Annotations

As part of the process of manually defining a class that is persistence-capable, the required annotations must (among other things)

To correctly define these methods, you must know how PSE/PSE Pro makes persistent objects accessible and what methods are available to create and access individual fields in an object.

Making Persistent Objects Accessible

The Persistent.fetch() method makes the contents of a persistent object available to be read by an application. The Persistent.dirty() method makes the contents of a persistent object available to be updated by an application.

To execute a fetch() or dirty() call, PSE/PSE Pro first checks whether or not a fetch() or dirty() call was already invoked on the object in the current transaction. If it was, PSE/PSE Pro does nothing and the program continues. If it was not, PSE/PSE Pro executes the method.

Call to initializeContents()

When PSE/PSE Pro retrieves a persistent object, it calls the initializeContents() method that you defined. The initializeContents() method calls methods on GenericObject to obtain the field values for the persistent object. The result is that your program has access to the desired data.

Description of GenericObject

PSE/PSE Pro provides the GenericObject class for transferring data between a database and a Java application or applet. A generic object represents an object's data as it is stored in the database. A generic object is a temporary buffer that PSE/PSE Pro uses while it is copying data from the database into a persistent object or writing data into the database from a persistent object. PSE/PSE Pro creates instances of GenericObject as needed. You do not define subclasses of GenericObject nor do you create instances of GenericObject.

For an object that was not already retrieved, PSE/PSE Pro copies the contents of the object from the database into the GenericObject instance. It then passes this instance to the initializeContents() method defined in the persistence-capable class.

Call to flushContents()

Suppose you called the dirty() method on a persistent object and modified it. To update the object in the database, commit the transaction. This causes PSE/PSE Pro to create an instance of GenericObject to hold the contents of your object. Then PSE/PSE Pro calls the flushContents() method that you defined when you defined the persistence-capable class.

The flushContents() method must call methods on the GenericObject instance that store the object's field values in the generic object. PSE/PSE Pro calls the flushContents() method as needed to copy the new contents of the object into the database.

Creating Fields

PSE/PSE Pro provides the Field class to represent a Java field in a persistent object. When you define a persistence-capable class, you must define a getFields() method in the required ClassInfo subclass. This method provides a list of the nonstatic fields (also called instance variables) whose values are being stored and retrieved.

Description of getFields()

The getFields() method must return an array that contains the nonstatic persistent object fields in order of field number. This array must include only those fields defined in the persistence-capable class and not any inherited fields.

Field numbers represent the position of a nonstatic field within the list of all nonstatic fields defined for the class and its superclasses. The first field has field number 1. (Note that the first field number is not 0.)

Order of fields

When you define the getFields() method in the ClassInfo subclass, you determine the order, and hence the number, of each field even though you do not explicitly assign any numbers. PSE/PSE Pro assigns the numbers according to the order in which the values are returned from the field create methods defined in the getFields() method. The field numbers are consecutive with no gaps. For example:

Example

public Field[] getFields() { return fields; }
      private static Field[] fields = {
            Field.createString("name"),
            Field.createInt("age"),
            Field.createClassArray("children", 
                  "COM.odi.demo.people.Person", 1)
      };
The definition above causes PSE/PSE Pro to associate 1 with the name field, 2 with the age field, and 3 with the children field.

When you define the initializeContents() and flushContents() methods, you must specify the correct field number for each field that the methods get and set.

Creation methods

The Field class provides a create method for each Java data type. Minimally, the create methods on the Field object

There are separate create methods for singleton and array fields of each primitive type. There are also string fields, class fields, and interface fields. The complete list of Field create methods is in Methods for Creating Fields and Accessing Them in Generic Objects.

Getting and Setting Generic Object Field Values

As described earlier, PSE/PSE Pro provides the GenericObject class to transfer objects between the database and an application. Consequently, when you define a persistence-capable class, you must define the initializeContents() method to retrieve values from fields in instances of GenericObject, and the flushContents() method to set values in fields of instances of GenericObject.

When you define the initializeContents() and flushContents() methods you must use a method that is appropriate for the type of each field in the instance of GenericObject. For example, for each character field, you must use the

There is a different method for getting and setting each Java type. To get or set an array of any type you define the getArrayField() and setArrayField() methods, respectively. In the initializeContents() method be sure to specify the methods that get the values. In the flushContents() method be sure to specify the methods that set the values. The methods that get and set fields in a generic object are listed in the table in Methods for Creating Fields and Accessing Them in Generic Objects.

Methods for Creating Fields and Accessing Them in Generic Objects



Kind of Field Method That Operates on It
Single byte (byte)

Field.createByte()
GenericObject.getByteField()
GenericObject.setByteField()
Array of bytes (byte[])
Field.createByteArray()
GenericObject.getArrayField()
GenericObject.setArrayField()
Single character (char)
Field.createChar()
GenericObject.getCharField()
GenericObject.setCharField()
Array of characters (char[])
Field.createCharArray()
GenericObject.getArrayField()
GenericObject.setArrayField()
Single 16-bit integer (short)
Field.createShort()
GenericObject.getShortField()
GenericObject.setShortField()
Array of 16-bit integers (short[])
Field.createShortArray()
GenericObject.getArrayField()
GenericObject.setArrayField()
Single 32-bit integer (int)
Field.createInt()
GenericObject.getIntField()
GenericObject.setIntField()
Array of 32-bit integers (int[])
Field.createIntArray()
GenericObject.getArrayField()
GenericObject.setArrayField()
Single 64-bit integer (long)
Field.createLong()
GenericObject.getLongField()
GenericObject.setLongField()
Array of 64-bit integers (long[])
Field.createLongArray()
GenericObject.getArrayField()
GenericObject.setArrayField()
Single 32-bit floating-point number (float)
Field.createFloat()
GenericObject.getFloatField()
GenericObject.setFloatField()
Array of 32-bit floating-point numbers (float[])
Field.createFloatArray()
GenericObject.getArrayField()
GenericObject.setArrayField()
Single 64-bit floating-point number (double)
Field.createDouble()
GenericObject.getDoubleField()
GenericObject.setDoubleField()
Array of 64-bit floating-point numbers (double[])
Field.createDoubleArray()
GenericObject.getArrayField()
GenericObject.setArrayField()
Single Boolean value (boolean)
Field.createBoolean()
GenericObject.getBooleanField()
GenericObject.setBooleanField()
Array of Boolean values (boolean[])
Field.createBooleanArray()
GenericObject.getArrayField()
GenericObject.setArrayField()
Single string value (String)
Field.createString()
GenericObject.getStringField()
GenericObject.setStringField()
Array of string values (String[])
Field.createStringArray()
GenericObject.getArrayField()
GenericObject.setArrayField()
A class
Field.createClass()
GenericObject.getClassField()
GenericObject.setClassField()
A class array
Field.createClassArray()
GenericObject.getArrayField()
GenericObject.setArrayField()
An interface
Field.createInterface()
GenericObject.getInterfaceField()
GenericObject.setInterfaceField()
An interface array
Field.createInterfaceArray()
GenericObject.getArrayField()
GenericObject.setArrayField()



[previous] [next]

doc@odi.com
Copyright © 1997 Object Design, Inc. All rights reserved.

Updated: 05/13/97 12:19:57