However, you might choose the manual method if you want to
You must explicitly postprocess or manually annotate each class that you want to be persistence-capable. The capacity for an object to be stored in a databases is not inherited when you subclass a persistence-capable class.
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 these steps to annotate your program so that classes you define are persistence-capable.
If you will be running your application in an environment that allows the unrestricted use of the Java reflection API, public or abstract classes with hollow object constructors do not require ClassInfo classes. However, all classes that define indexable fields on objects stored in peer (COM.odi.coll )collections do require ClassInfo classes.
About interfaces
Interfaces are always persistence-capable. You must specify them when you run the postprocesor, but other than that you do not need to do anything to make an interface persistence-capable. Implementing the IPersistent Interface
Every persistence-capable class must implement the IPersistent interface or be a subclass of a class that implements it. As with any interface, every method defined in the IPersistent interface must be defined in a class that implements IPersistent. If you do not define all methods, you can run the postprocessor to insert the missing methods. If you do not define all methods, and you do not run the postprocessor, you receive a compilation error. Defining the Required Fields
The following code must be in your class definition. You can add this code yourself, or you can run the postprocessor to add it.
transient private COM.odi.imp.ObjectReference ODIRef; transient public byte ODIObjectState;The ODIRef field stores a reference. The ODIObjectState field holds some object state bits. The underlying run-time classes in ObjectStore access these fields through the IPersistent accessor methods as needed.
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 implements IPersistent through a superclass, you must also initialize superclass fields by invoking initializeContents() on the superclass.
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 implements IPersistent through a superclass, you must also flush superclass fields by invoking flushContents() on the superclass.
public void clearContents()Here is an example:
public void clearContents() { name = null; age = 0; children = null; }If the class you are annotating implements IPersistent through a superclass, you must also clear superclass fields by invoking clearContents() on the superclass.
If you add the code yourself, it must look like this:
public COM.odi.imp.ObjectReference ODIgetRef() { return ODIRef; } public void ODIsetRef(COM.odi.imp.ObjectReference objRef) { ODIRef = objRef; } public byte ODIgetState() { return ODIObjectState; } public void ODIsetState(byte state) { ODIObjectState = state; }
With some exceptions, before your application can access the contents of an object, it must call the
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.
Exceptions
You do not need to call the fetch() or dirty() method on instances of primitive wrapper classes. If you do call fetch() or dirty() on these objects, nothing happens and processing continues.
public IPersistent 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.
getClassDescriptor()
Define the public getClassDescriptor() method to obtain the class object for your class. For example:
public Class getClassDescriptor() throws ClassNotFoundException { 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) };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 annotated 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. Class definition
package COM.odi.demo.people; import COM.odi.*; // Define a class that implements IPersistent: class Person implements IPersistent { // Fields: String name; int age; Person children[]; // Other fields ... // Constructor: public Person(String name, int age, Person children[]) { this.name = name; this.age = age; this.children = children; } // Hollow object constructor: public Person(ClassInfo info) { } // Accessor methods that have been modified to call // the fetch() and dirty() methods: public String getName() {ObjectStore.fetch(this); return name; } public void setName(String name) {ObjectStore.dirty(this); this.name = name; } public int getAge() {ObjectStore.fetch(this); return age; } public void setAge(int age) {ObjectStore.dirty(this); this.age = age; } public Person[] getChildren() {ObjectStore.fetch(this); return children; } public void setChildren(Person children[]) { ObjectStore.dirty(this); this.children = children; } // Other methods ... // Additions required for ObjectStore: // 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, myClassInfo); age = handle.getIntField(2, myClassInfo); children = (Person[])handle.getArrayField(3, myClassInfo); } // Define the flushContents() method to copy the // contents 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; } // Hook methods. public void preFlushContents() { } public void preClearcontents() { } public void postInitializecontents() { } public void preDestroyPersistent() { } // Define the ODIRef and ODIObjectState fields and // their accessor methods. transient private COM.odi.imp.ObjectReference ODIRef; transient public byte ODIObjectState; public COM.odi.imp.ObjectReference ODIgetRef() { return ODIRef; } public void ODIsetRef(COM.odi.imp.ObjectReference objRef) { ODIRef = objRef; } public byte ODIgetState() { return ODIObjectState; } public void ODIsetState(byte state) { ODIObjectState = state; } // Create an instance of the subclass of ClassInfo and // register that instance: static ClassInfo myClassInfo = ClassInfo.get("COM.odi.people.Person"); }
// Define the subclass of ClassInfo. A recommended naming // convention is to prefix the name of your persistence-capable // class to "ClassInfo". package COM.odi.demo.people; import COM.odi.*; public class PersonClassInfo extends ClassInfo { // Define a create() method to create instances of your // class with default field values. The method // calls the hollow object constructor and passes this, // which is an instance of the ClassInfo subclass: public IPersistent create() { return new Person(this); } // 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() throws ClassNotFoundException { 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", "COM.odi.demo.People.Person", 1) }; }It does not matter whether the ClassInfo class explicitly implements IPersistent or inherits from a class that implements IPersistent.
ClassInfo is an abstract class for managing schema information for persistence-capable classes. ObjectStore requires the schema information to manage the object. If you do not explicitly define a ClassInfo class, ObjectStore uses the Java reflection API to creates the needed information at runtime.
After you perform the steps described in this section, you can store instances of your class in a database.
ObjectStore 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. It discusses the following topics:
This is not a problem if you never use the object as a key in a persistent hash table or other structure that uses the hashCode() method to locate objects. If you do use the object as a key, the hash table or other structure that relies on the hashCode() method might become corrupted when you bring the objects back from the database.
To resolve this problem, you can define your own hashCode() method and base it on the contents of the objects so it returns the same thing every time. The signature of this method must be
public int hashCode()
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.
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.
class Some { int a; int b; int aPlusb; initializeContents(GenericObject, go) { a=go.getField(1, SomeClassInfo); b=go.getField(2, SomeClassInfo); c=a+b; } ... }In a separate file:
public class SomeClassInfo static Field[] fields= { field.createInt"a"); field.createInt("b"); }
Variable initializers
If you manually annotate a class, you should avoid using variable intializers to initialize persistent fields of persistence-capable objects. Instead, perform the initialization in the contructor. This is because the values computed by the variable initializer expression are typically overwritten by the COM.odi.IPersistent.initializeContents() method. When an object is actually fetched from the database, the fields are initialized with their correct persistent values. Example
An example of how you might use transient-only and persistent-only fields is in the demo directory that is included in ObjectStore. 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 implements IPersistent { transient private COM.odi.imp.ObjectReference ODIref; transient public byte ODIobjectState; transient Point a; transient Point b; static ClassInfo classInfo = ClassInfo.register(new RectangleClassInfo()); public COM.odi.imp.ObjectReference ODIgetRef() { return ODIref; } public void ODIsetRef(COM.odi.imp.ObjectReference objRef) { ODIref = objRef; } public byte ODIgetState() { return ODIobjectState; } public void ODIsetState(byte state) { ODIobjectState = state; } Rectangle(Point a, Point b) { this.a = a; this.b = b; } void describe() { System.out.println("Rectangle with two points:"); a.describe(); b.describe(); } /* Annotations for persistence. */ Rectangle(ClassInfo ignored) {} public void initializeContents(GenericObject handle) { a = new Point(handle.getIntField(1, classInfo), handle.getIntField(2, classInfo)); b = new Point(handle.getIntField(3, classInfo), handle.getIntField(4, classInfo)); } public void flushContents(GenericObject handle) { handle.setIntField(1, a.x, classInfo); handle.setIntField(2, a.y, classInfo); handle.setIntField(3, b.x, classInfo); handle.setIntField(4, b.y, classInfo); } public void clearContents() { a = null; b = null; } public void postInitializeContents() {}; public void preFlushContents() {}; public void preClearContents() {}; public void preDestroyPersistent() {}; /* This class is never used as a persistent hash key. */ public int hashCode() { return super.hashCode(); } }In a separate file:
public class RectangleClassInfo extends ClassInfo { public IPersistent create() { return new Rectangle(this); } public Class getClassDescriptor() throws ClassNotFoundException { return Class.forName("COM.odi.demo.rep.Rectangle"); } public Field[] getFields() { return fields; } private static Field[] fields = { Field.createInt("ax"), Field.createInt("ay"), Field.createInt("bx"), Field.createInt("by"), };}
To make a class persistence-aware, modify each method that references
A persistence-aware class includes the fetch() and dirty() annotations. It does not include the other annotations that are required for a class to be persistence-capable.
For example, if you define the Boat class, the name of the associated subclass of ClassInfo must be BoatClassInfo.
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. The ClassInfo subclass associated with X does not extend the ClassInfo subclass associated with Y.
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.
To execute a fetch() or dirty() call, ObjectStore first checks whether a fetch() or dirty() call was already invoked on the object in the current transaction. If it was, ObjectStore does nothing and the program continues. If it was not, ObjectStore executes the fetch() or dirty() call as required.
For an object that was not already retrieved, ObjectStore 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 ObjectStore to create an instance of GenericObject to hold the contents of your object. Then ObjectStore 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. ObjectStore calls the flushContents() method as needed to copy the new contents of the object into the database.
Creating Fields
ObjectStore 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. The order in which they appear in the array implies their associated field numbers. This array must include only those fields defined in the persistence-capable class and not any inherited fields. 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. ObjectStore 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:
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 ObjectStore 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.
Getting and Setting Generic Object Field Values
As described earlier, ObjectStore 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.
Methods for Creating Fields and Accessing Them in Generic Objects
Updated: 10/07/98 08:46:12