This chapter discusses the following topics:
Storing Objects
Troubleshooting OutOfMemoryError
Using External References to Stored Objects
Updating Objects in the Database
Committing Transactions to Save Modifications
Evicting Objects to Save Modifications
Aborting Transactions to Cancel Changes
Destroying Objects in the Database
Default Effects of Various Methods on Object State
Transient Fields in Persistence-Capable Classes
Troubleshooting Access to Persistent Objects
Storing Objects
ObjectStore's Java API preserves the automatic storage management semantics of Java. Objects become persistent when they are referenced by other persistent objects. This is called persistence by reachability. The application defines persistent roots and, when it commits a transaction, ObjectStore finds all objects reachable from persistent roots and stores them in the database.
Caution
If you are going to have objects in one segment that refer to objects in another segment, it is crucial that you do some planning before you store any objects. Implementing Cross-Segment References for Optimum Performance provides information about the issues to consider. It is important to familiarize yourself with this information because doing so can help you avoid problems.
What Is Reachability?
An object B is considered to be reachable from object A when A contains a reference to B. B is also reachable from A when A contains a reference to some object and that object contains a reference to B. There are no limits to levels of reachability. Situations to Avoid
When a transaction commits, you must ensure that objects in different segments do not refer to the same transient object. If this situation exists, ObjectStore cannot determine which segment to store the transient object in. Consequently, ObjectStore throws AbortException, aborts the transaction, and informs you that an unexported object is referred to by an object in another segment. Storing Java-Supplied Objects
Some Java-supplied classes are persistence-capable. Others are not persistence-capable and cannot be made persistence-capable. A third category of classes can be made persistence-capable, but there are important issues to consider when you do so. Be sure to read Java-Supplied Persistence-Capable Classes.
Working with Database Roots
A root is a reference to an individual object. You can get by with a single root, but you might find it convenient to have more. In general, you do not want every object in the database to be associated with a root. This is bad for performance. Each root refers to exactly one object. More than one root can refer to the same object. You cannot navigate backwards from the referenced object to the database root.
You must create a database root inside a transaction. Call the Database.createRoot() method on the database in which you want to create the root. The method signature for this instance method on Database is
public void createRoot(String name, Object object)The name you specify for the root must be unique in the database. If it is not unique, DatabaseRootAlreadyExistsException is thrown. The object that you specify to be referred to by the root can be either transient and persistence-capable, or persistent (including null). If it is not yet persistent, ObjectStore immediately makes it persistent. ObjectStore migrates it to the default segment, which is the segment returned by Database.getDefaultSegment().
db.createRoot("Root1", anObject);
db.createRoot("Root2", anObject);
db.createRoot("MyRootName", anObject);
Retrieving Root Objects
When you retrieve a root object, you obtain a reference to the object that is the value of the root. For example, suppose you assign an OSVector object, myOSVector, to a root named "myOSVectorRoot". When you get myOSVectorRoot, you receive a reference to myOSVector. ObjectStore does not fetch the entire vector. Now you can obtain a reference to any object in the vector. For example, you can get a reference to the fifth element in the vector like this:
AnObject obj = (AnObject)myOSVector.elementAt(5);The result of this call is that you have a reference to the fifth element in myOSVector. ObjectStore has fetched only the contents of the myOSVector object, which includes references to the elements in the vector. It has not yet fetched the contents of any vector elements. When you try to access the contents of an element in the vector, ObjectStore fetches the data for that element. When you retrieve an element of an OSVector object, ObjectStore does not fetch all the elements of the OSVector. It fetches only the particular element you want to read or modify.
db.createRoot("foo", new Integer(5));
This assigns the value 5 to the root named foo.You cannot directly store primitive values in a database. You can define a primitive value as a field in a persistence-capable object.
public void setRoot (String name, Object object)If ObjectStore cannot find the specified root, DatabaseRootNotFoundException is thrown.
Destroying a Database Root
To destroy a database root, call the destroyRoot() method on the database that contains the root that you want to destroy. An update transaction must be in progress. Specify the name of the root. If ObjectStore cannot find the specified root, it throws DatabaseRootNotFoundException. The method signature is
public void destroyRoot (String name)This has no effect on the referenced object, except that it is no longer accessible from that root. It might still be the value of another root, or it might be pointed to by some other persistent object. If a value of a root is no longer referenced after the root is destroyed, the object becomes unreachable. You can invoke ObjectStore.destroy() on it while you still have a reference to it. Alternatively, you can run the persistent garbage collector to remove all unreachable objects. See Performing Garbage Collection in a Database.
Destroying the Object Referred To by a Database Root
If you want to destroy the object that a database root refers to and you want to continue to use that database root, you must set the root to refer to null or another object before you destroy the object that the root refers to. If you do not do this and you try to use the root, ObjectStore throws ObjectNotFoundException. This is because the root refers to a destroyed object. For example, the correct sequence is something like this:
Object object = db.getRoot("username");
db.setRoot("username", null);
ObjectStore.destroy(object);
Think of a database root as the root of a tree. From the root, you can climb the tree. For many applications, a root is some kind of container, such as an instance of OSHashtable or OSVector, or an array. After you create one or more database roots, you create other objects that are referred to by fields of the objects that the roots refer to. These objects become persistent when you commit the transaction in which you create them. In a subsequent transaction, you can look up the root objects by name, and navigate from them to any other reachable persistent objects.
Too many roots can cause performance problems. The maximum practical number of roots within a database isapproximately 100. Databases store roots in a vector and ObjectStore uses a linear search to find roots. To avoid long look-up times, restrict the number of database roots.
When a transaction commits, ObjectStore releases references to all objects except those that are exported. ObjectStore uses JDK weak references to refer to exported objects. When an object is referred to by only weak references, it can be garbage collected. However, performing an explicit Java VM GC does not necessarily cause such weakly referenced objects to be collected and so free their space. Typically, the Java GC frees weakly referenced objects when it needs to grow the VM heap space. So, eventually you should see the storage for these objects being reclaimed.
Try running with the -verbosegc option to the Java VM. If the weakly referenced objects are never freed, it is likely to be for one of the following reasons:
This section discusses the following topics:
The signature for the getRoot() method is
public Object getRoot(String name)
List of all roots
To obtain a list of the roots in a database, call the getRoots() method on the database. The signature of this method is
public DatabaseRootEnumeration getRoots()
public static Database of(Object object)
If the specified object has been stored in a database, ObjectStore returns the database in which it is stored. The specified object must be a persistent primary Java object or a Java peer object. If you specify a Java peer object, it can identify a persistent or transient C++ object. If the specified object is a peer object for a transient object, the method returns the transient database.
public static boolean isPersistent(Object object)
If the specified object has been stored in a database, ObjectStore returns true. The specified object must not be a stale persistent object. If it is, ObjectStore throws ObjectException.
public SegmentObjectEnumeration getObjects()This method returns a SegmentObjectEnumeration object. After you have this object, you can use the following methods to iterate through the objects in the enumeration:
The Segment.getObjects() method has an overloading that takes a java.lang.Class object as an argument and returns an iterator over all objects of that type in the database. The type can be an interface, class, or array type.
If your session or another session adds an object to a segment after you create an enumeration, the enumeration might or might not include the new object. If it is important for the enumeration to accurately include all objects, you should create the enumeration again.
Locking Objects
You can lock an object if you want to ensure that no other session can access it. This is useful when you want to ensure that a particular operation is not interrupted. See Locking Objects, Segments, and Databases to Ensure Access.
Using External References to Stored Objects
Outside a database, you might want to represent a reference to an object in a database. This external reference can be in an ASCII file or you might want to transmit it over a serial network connection. External references can be especially useful when you write a distributed application server that processes requests for many clients. This includes client/server applications that are based on Java RMI, ObjectStore ObjectForms, or the Object Management Group's Common Object Request Broker Architecture (CORBA).
ExternalReference myExRef = new ExternalReference(myPObj); Database refToDb = myExRef.getDatabase(); int segId = myExRef.getSegmentId(); int loc = myExRef.getLocation();An object represented by an external reference must be a persistent object or null. It can be a Java peer object only if it identifies a persistent C++ object. If you try to create an external reference to a peer object that identifies a transient C++ object, ObjectStore throws DatabaseException.
A database referred to in a call to an ExternalReference constructor or method must exist in the file system at the time of the call and must be open.
public ExternalReference(Object o)
public ExternalReference(Database d, int segid, int loc)
When an object is exported, ObjectStore uses its export ID in the external reference. This prevents the object or its tombstone from being garbage collected. It also allows ObjectStore to find the object if it is moved in a segment reorganization.
To perform step 1, you can use the three-argument ExternalReference constructor:
public ExternalReference(Database d, int segid, int loc);When you use this constructor, a transaction in progress is not required. For example:
ExternalReference anExRef = new ExternalReference(To obtain the corresponding object, call ExternalReference.getObject(). For example:
refToDb, segId, loc);
Object myPObj = myExRef.getObject();
public boolean equals(Object obj)
public void setObject(Object o)For example, suppose myExRef was constructed for myPObj. Then you retrieved the database, segment ID, and location values and stored them in some file outside the database. Now you want to do the same thing for another persistent object, herPObj:
myExRef = ExternalReference.setObject(herPObj);The myExRef external reference object now contains information for herPObj. You can use the ExternalReference.get xxx() methods to obtain the values you can store outside the database.
myExRef.setDatabase(refToDb); myExRef.setSegmentId(segId); myExRef.setLocation(loc); hisPObj = myExRef.getObject();
toString()
The toString() method is straightforward to use. You call the method on an ExternalReference object that contains values for a persistent object. The method signature is
public String toString()
public static ExternalReference fromString(String string)
Whether you commit a transaction or evict an object, you can specify the state of objects after the operation. To specify the state that makes the most sense for your application, an understanding of the following background information is important:
Instructions for invoking commit() or evict() follow this background information.
In a single session, the Java VM never creates two distinct objects that both represent the same object in the database.
public class City {
String name;
int population;
}
public class State {
City capital;
String name;
int population;
}
City boston = new City("Boston", 1000000);
State massachusetts = new State(
boston, "Massachusetts", 20000000);
OSHashtable cities = new OSHashtable();
cities.put("Boston", boston);
OSHashtable states = new OSHashtable();
states.put("Massachusetts", massachusetts);
db.createRoot("cities", cities);
db.createRoot("states", states);
This creates
OSHashtable cities = (OSHashtable) db.getRoot("cities");
OSHashtable states = (OSHashtable) db.getRoot("states");
City boston1 = cities.get("Boston");
State massachusetts = states.get("Massachusetts");
City boston2 = massachusetts.capital;
if (boston1 == boston2)
System.out.println("same");
else
System.out.println("not the same");
If you use cities to reach boston1 and you modify boston1, you can then use states to access the updated version as boston2.
Transaction tr = Transaction.begin(ObjectStore.UPDATE);
City Boston = new City("Boston", 1000000);
OSHashtable cities = new OSHashtable();
cities.put("Boston", Boston);
db.createRoot("cities", cities);
tr.commit();
Transaction tr = Transaction.begin(ObjectStore.UPDATE);
City Boston = new City("Boston", 1000000);
State massachusetts = new State(
Boston, "Massachusetts", 20000000);
OSHashtable states = new OSHashtable();
states.put("Massachusetts", massachusetts);
db.createRoot("states", states);
tr.commit();
If you follow the two different paths through the database to the two different Boston objects, you obtain two discrete persistent objects. If you update one Boston object, ObjectStore does not update the other Boston object. If you execute the same code fragment as you did for the first code sample, the result is "not the same". That is, (boston1 == boston2) returns false.
String pooling causes some strings to be the same, even when you create them separately. If you call new multiple times to create multiple String objects, these separately created objects might be identical. See Description of COM.odi.stringPoolSize. If you explicitly migrate the string to the database, it prevents ObjectStore from using string pooling.
A String or primitive wrapper object that you create with a single call to new might be represented by more than one persistent object. Usually, this does not matter for Stings and primitive wrapper objects because it is their value and not their identity that matters. If identity does matter, you can explicitly migrate wrapper objects into the database.
Identity across transactions
ObjectStore maintains the identity of referenced objects across transactions within the same session. The following code fragment provides an example of this. It displays "same".
public
class Person {
// Fields in the Person class:
String name;
int age;
Person children[];
Person father;
// Constructor:
public Person(String name, int age,
Person children[], Person father) {
this.name = name;
this.age = age;
this.children = children;
this.father = father;
}
static void testIdentity() {
// Omit open database calls
Transaction tr = Transaction.begin(ObjectStore.UPDATE);
Person children[] = { null, null };
Person tim = new Person("Tim", 35, children, null);
Person sophie = new Person("Sophie", 5, null, tim);
children[0] = sophie;
db.createRoot("Tim", tim);
tr.commit();
Transaction tr = Transaction.begin(ObjectStore.UPDATE);
tim = (Person)db.getRoot("Tim");
Person joseph = new Person("Joseph", 1, null, tim);
tim.getChildren()[1] = joseph;
tr.commit();
Transaction tr = Transaction.begin(ObjectStore.READONLY);
tim = (Person)db.getRoot("Tim");
sophie = tim.getChildren()[0];
joseph = tim.getChildren()[1];
if (sophie.getFather() == joseph.getFather())
System.out.println("same");
else
System.out.println("not the same");
tr.commit();
}
If the system property COM.odi.disableWeakReferences is set to false (the default), the references in the object table are weak references, which means that they do not interfere with the Java garbage collector. The Java garbage collector can function in the same way that it normally does. That is, if a Java program does not have any references to a persistent object (the copy in your Java program), other than through the ObjectStore object table, the object can be garbage collected. (The object in the database, of course, is not garbage collected.)
If the modifications contain references to objects that are not persistence-capable, ObjectStore throws AbortException. The AbortException.getOriginalException() method returns the object that causes the exception.
If objects were stored in the database for the first time during this transaction, the copies of these objects in your Java program are included in the group of persistent objects.
public void commit()The second overloading has an argument that specifies the state of persistent objects after the commit operation. The method signature is
public void commit(int retain)
Synchronization
Synchronizing on a persistent object is another way to retain a reference to that object after a transaction ends. To do this, an application must end a transaction with a retain type other than stale. When objects become stale, ObjectStore does not maintain transient object identity. The synchronized state of an object is not saved persistently. Making Persistent Objects Stale
When you call the commit() method with no argument, ObjectStore makes all persistent objects stale. Stale persistent objects are not accessible and their contents are set to default values. ObjectStore reclaims the entry in the Object Table for the stale object and the object loses its persistent identity.
If your Java program still has references to stale objects, any attempt to use those references (such as by accessing a field or calling a method on the object) causes ObjectStore to throw ObjectException. Therefore, your application must discard any references to persistent objects when it calls this overloading of commit().
Objects available for garbage collection
This overloading of commit() also discards any internal ObjectStore references to the copies of the objects in your Java program. When your application makes an object stale, ObjectStore makes any references from the stale object to other objects null. This makes the referenced objects, which can be persistent or transient, available for garbage collection if there are no other references to them from other objects. Accessing objects again
You can reaccess the same objects in the database in subsequent transactions. To do so, look up a database root and traverse to objects from there, or reference them through hollow objects. ObjectStore refetches the contents of the object and creates a new active persistent object. The new object has a new transient identity and the same persistent identity as the object that became stale. For example:
Foo foo = myDB.getRoot("A_FOO");
ExternalReference fooRef = new ExternalReference(foo);
ObjectStore.evict(foo, ObjectStore.RETAIN_STALE);
Foo fooTwo = myDB.getRoot("A_FOO"); // refetch from database
ExternalReference fooRefTwo = new ExternalReference(fooTwo);
// At this point (foo == fooTwo) returns false,
// but (fooRef.equals(fooTwoRef)) returns true.
References to these objects remain valid; the application can use them in a subsequent transaction. If a hollow object is accessed in a subsequent transaction, ObjectStore refreshes the contents of the object in your Java program with the contents of the corresponding object in the database.
If there are any hollow objects when you commit the transaction, ObjectStore retains these objects as hollow objects that you can use during the next transaction.
After this transaction and before the next transaction, your application can read the contents of any retained objects whose contents were also retained. The actual contents of the object in the database might be different because some other process modified it. Your application cannot modify these objects. An attempt to do so causes ObjectStore to throw NoTransactionInProgressException. Your application cannot access the contents of hollow retained objects. An attempt to do so causes ObjectStore to throw NoTransactionInProgressException.
Your program might return results that are inconsistent with the current state of the database.
ObjectStore cannot fetch any objects outside a transaction. This makes it difficult to ensure that methods can execute without throwing an exception. However, you can call ObjectStore.deepFetch() in the transaction to obtain the contents of all objects you might need. Of course, this increases the risk of the Java VM running out of memory.
Retaining collections not allowed
Peer objects, and therefore collection objects, have no data members and so, in Java, peer objects do not appear to be connected to other objects. Even if you explicitly iterate through the elements in a collection and then commit the transaction with ObjectStore.RETAIN_READONLY, you cannot access the collection outside a transaction. However, if the collection elements are not themselves peer objects or collections, you can manipulate them outside a transaction, but you cannot use the collection to access them. You must explicitly read them in the transaction, retain them, and then access them directly or through another nonpeer object. Troubleshooting: NoTransactionInProgressException
Between transactions, you might try to read an object that you thought you retained, and receive NoTransactionInProgressException. Often, the cause of this is that you retained a reference to the object but not the contents of the object.
Inside a transaction, ObjectStore automatically fetches the contents of objects as you read the objects. Outside a transaction, if a reference to an object, but not the contents of the object, was retained, ObjectStore throws NoTransactionInProgressException.
Retaining Persistent Objects as Writable
Call commit(ObjectStore.RETAIN_UPDATE) to retain the copies of the objects in your Java program as readable and writable. ObjectStore maintains the contents of the persistent objects as they are at the end of the transaction.
Sometimes, the contents of an object are not available in a transaction when you expect that they are available. See Troubleshooting: NoTransactionInProgressException.
When retained objects are exported, ObjectStore can correctly retain them after schema evolution.
Consequently, you must follow at least one of these rules:
public static void evict(Object object)The second overloading has an additional argument that specifies the state of the evicted object after the eviction. The method signature is
public static void evict(Object object, int retain)
You might evict an object that has instance variables that are transient strings. ObjectStore migrates such strings to the database and stores them in the same segment as the evicted object. As part of the eviction process, ObjectStore evicts the just-stored string with a specification of ObjectStore.RETAIN_READONLY. Consequently, after the eviction, the migrated string remains readable.
If you try to evict an object when that object refers to a transient object that is not persistence-capable, ObjectStore throws ObjectNotPersistenceCapableException and does not perform the eviction. The exception message provides the class of the object that is causing the problem.
If you try to evict an object when the object refers to an unexported object in another segment, ObjectStore throws ObjectNotExportedException and cancels the evict operation.
Setting the Evicted Object to Be Stale
When you invoke evict(Object), ObjectStore makes the evicted object stale. ObjectStore resets the contents of the copy of the object in your Java program to default values and makes the object inaccessible. Any references to the evicted object are stale, and your application should discard them. The copy of the object in your Java program becomes available for Java garbage collection. Advantage
The advantage of using the evict(Object) method is that the evicted object and all objects it refers to become available for Java garbage collection (provided that they are not referenced by other objects in the Java program). Disadvantage
The disadvantage is that any references to the evicted object become stale. If you try to use the stale references, ObjectStore throws ObjectException. Alternative method
A call to evict(Object, ObjectStore.RETAIN_STALE) is identical to a call to evict(Object). Setting the Evicted Object to Be Hollow
When you invoke evict(Object, ObjectStore.RETAIN_HOLLOW), ObjectStore makes the evicted object hollow. ObjectStore resets the contents of the copy of the object in your Java program to default values. References to the evicted object continue to be valid. Advantage
The reason to use the evict(Object, ObjectStore.RETAIN_HOLLOW) method is that the object and all objects it refers to become available for Java garbage collection (provided that they are not referenced from other objects in the Java program). Setting the Evicted Object to Remain Active
When you invoke evict(Object, ObjectStore.RETAIN_READONLY), ObjectStore
It might seem strange to evict an object with ObjectStore.RETAIN_READONLY and yet be able to modify the object after the eviction. The specification of READONLY in this context means that, as of this point in time, the evicted object has been read but not modified. The changes have already been saved, but not committed. The contents are still available and can be read or updated.
Evicting All Persistent Objects
You can evict all persistent objects with one call to evictAll(). The method signature is
public static void evictAll(int retain)For the retain argument, you can specify
class C {
String x;
String y;
void function() {
System.out.println(x);
ObjectStore.evict(this);
System.out.println(y);
}
}
Before the first call to println(), the object is accessible. After the call to evict(), the y field is null and the second println() call fails. There are more complicated scenarios for this problem, which involve subroutines that call evict() and cause problems in the calling functions. This problem can occur in a single thread. If there are multiple cooperating threads, each thread must recognize what the other thread is doing.
It is the responsibility of the application to ensure that the object being evicted is not the this argument of any method that is currently executing.
Committing Transactions After Evicting Objects
In a transaction, you might evict some objects and specify their state to be hollow or active. If you then commit the transaction and cause the state of persistent objects to be stale, this overrides the hollow or active state set by the eviction. If you commit the transaction and cause the state of persistent objects to be hollow, this overrides an active state set by eviction. For example:
Transaction tr = Transaction.begin(ObjectStore.UPDATE);
Trail trail = (Trail) db.getRoot("greenleaf");
GuideBook guideBook = trail.getDescription();
ObjectStore.evict(guideBook, ObjectStore.RETAIN_READONLY);
tr.commit();
After the transaction commits, the application cannot use guideBook. Committing the transaction without specifying a retain argument makes all persistent objects stale. This overrides the RETAIN_READONLY specification when guideBook was evicted.
If you specified commit(ObjectStore.RETAIN_STALE), there are no objects to evict after the transaction commits.
If you invoked commit() with any other retain value, you can call evict() or evictAll() with the value of the retain argument as RETAIN_STALE or RETAIN_HOLLOW. If you specify RETAIN_READONLY, ObjectStore does nothing.
Outside a transaction, if you make any changes to the objects you evict, ObjectStore discards these changes at the start of the next transaction. They are not saved in the database.
This section discusses the following topics:
Setting Persistent Objects to the Default State
To abort a transaction and set the state of persistent objects to the state specified by Transaction.setDefaultAbortRetain(), call the abort() method. The default state is stale. The method signature is
public void abort()For example:
tr.abort();
public void setDefaultAbortRetain(int newRetain)The values you can specify for newRetain are the same values you can specify when you call abort() with a retain argument. These values are described in the next section.
public void abort(int retain)The following example aborts the transaction and specifies that the contents of the active persistent objects should remain available to be read:
tr.abort(ObjectStore.RETAIN_READONLY);The values you can specify for retain are described below. The rules for Java garbage collection of objects retained from aborted transactions are the same as for objects retained from committed transactions.
If you try to modify a persistent object before the next transaction, ObjectStore throws NoTransactionInProgressException. If you modified any persistent objects during the aborted transaction, ObjectStore discards these modifications and makes these objects hollow as part of the abort operation.
During the next transaction, the contents of persistent objects that were not modified during the aborted transaction are still available.
While you are between transactions, the changes that were aborted are still visible in the Java objects. At the start of the next transaction, ObjectStore discards the modifications and reads in the contents from the database. Objects that were read or modified in the aborted transaction can be modified between the aborted transaction and the next transaction. If you modify any persistent objects during or after the aborted transaction, ObjectStore discards these modifications and makes these object hollow at the start of the next transaction.
During the next transaction, the contents of persistent objects that were not modified during or after the aborted transaction are still available.
public static void destroy(Object object)The object you specify must be persistent or the call has no effect. The database that contains the object must be open-for-update and an update transaction must be in progress.
If the destroyed object either implements the IPersistent interface or is an array, you cannot access any of its fields after you destroy it.
After you invoke ObjectStore.destroy() on a primary Java object, ObjectStore leaves a tombstone. If you try to access the destroyed object, the tombstone causes ObjectStore to throw ObjectNotFoundException.
Destroying Objects That Refer to Other Objects
By default, when you destroy an object, ObjectStore does not destroy objects that the destroyed object references.
You can use preDestroyPersistent() to propagate the destroy operation to child objects that are referenced by the one being destroyed. If you do this, be careful that the child objects themselves are not referenced by other objects in the database. If an object attempts to use a reference to an explicitly destroyed object, ObjectStore throws ObjectNotFoundException. If you are not certain whether a specific object might be referenced elsewhere, it is better to avoid explicitly destroying the object. Let the persistent garbage collector do the job instead.
OSHashtable and OSVector
When you delete a COM.odi.util.OSHashtable or COM.odi.util.OSVector object, ObjectStore deletes the hash table or vector and its own internal data structures. ObjectStore does not delete the keys or elements that were inserted into the hash table or vector. Doing so might cause problems because other Java objects might refer to those objects.
However, sometimes you want to destroy the objects in a hash table or vector as well as the hash table or vector itself. Suppose you have a class in which one of the instance variables is a COM.odi.util.OSVector. You might want to ensure that whenever an instance of this class is destroyed, the OSVector and its contents are also destroyed. To do this, you can define a preDestroyPersistent() method on your class. Define this method to iterate over the elements in the vector, destroy each one, and then destroy the COM.odi.util.OSVector.
Types not destroyed
When you call the ObjectStore.destroy() method on an object, it does not destroy fields in the object that are
class C {
int i;
String s;
Double d;
}
You do not need to have preDestroyPersistent() call ObjectStore.destroy() on fields that contain primitive types.
If you are the programmer implementing the MyVector class, you have two choices:
public class MyVector {
private Object[] contents;
public addElement(Object o) {
contents[nextElement++] = o;
}
public void preDestroyPersistent() {
if (contents != null)
ObjectStore.destroy(contents);
}
}
If an object retains a reference to a destroyed object, ObjectStore throws ObjectNotFoundException when you try to use that reference. This might occur long after the referenced object was destroyed. To clean up this situation, set the reference in the referring object to null.
Unless you manually annotate your classes to make them persistence-capable, you do not write ObjectStore.fetch() or ObjectStore.dirty() calls in your application. The postprocessor inserts these calls automatically as needed.
The information in this table assumes that you are not specifying a retain argument with any of the methods that accept a retain argument.
| Method the Application Calls | Result |
|---|---|
| Transaction.commit() | Persistent objects become stale. |
| Transaction.abort() |
Persistent objects become stale.
|
Transient Fields in Persistence-Capable Classes
This section discusses
Behavior of Transient Fields
In a persistence-capable class, a field designated with the transient keyword behaves as follows:
When you specify the -noannotatefield option, follow it with a qualified field name.
The default behavior of the postprocessor is to insert calls to fetch() and dirty(). This is because transient fields need to be initialized in objects that are read from the database, and initialization might be based on persistent state. You can define the postInitializeContents() and preFlushContents() methods to maintain persistent and transient fields in parallel. The calls to fetch() and dirty() trigger the calls to these methods.
If the -noannotatefield behavior was the default, transient fields would not be initialized and there would be no error notification of this. Since this is not the default, you receive an exception if and when there is a problem.
If your class defines a finalize() method, the class file postprocessor inserts annotations at the beginning of the finalize() method that change the persistent object to a transient object. This makes it safe to access fields of the finalized object. However, if the object has not been fetched, the fields are in an uninitialized state.
Typically, the postprocessor creates a ClassInfo subclass for types you want to store in a database. If you do not run the postprocessor, you must define a ClassInfo subclass yourself. ObjectStore uses this ClassInfo subclass to register the class the first time ObjectStore encounters the class in your application. If ObjectStore cannot find the ClassInfo subclass that describes a type, that type is unregistered.
When ObjectStore encounters an object in a database, it determines whether there is already a Java object for the object in the database. If there is, ObjectStore uses that object. If there is not, ObjectStore checks to see if the type of the object is registered.
If the type is not registered, ObjectStore tries to load the ClassInfo subclass for the type and register it. ObjectStore uses the regular Java class loading mechanism. Usually, this means that ObjectStore searches your CLASSPATH. Depending on the Java implementation you are using, Java class loading can also involve Java ClassLoader objects, as described in the Java Language Specification.
If ObjectStore cannot load the ClassInfo subclass, it cannot register the type and consequently it cannot create a hollow object for the type. In this case, ObjectStore creates a new Java object of type UnregisteredType and uses it in place of the hollow object.
class Person {
Pet mypet;
Object mytrash;
}
You also have a database that contains one Person object. The value of the Person.mypet instance variable is an instance of the Pet class. The value of the Person.mytrash instance variable is an instance of the Shoe class. Now, suppose that the Pet class is an unregistered class. Your application opens the database and tries to read the Person object. This means ObjectStore must initialize the Person object. When ObjectStore recognizes that the Pet class is unregistered, it creates an UnregisteredType object. ObjectStore then tries to assign the mypet instance variable to the UnregisteredType object. The code to do this is something like this:
mypet = (Pet)(handle.getClassField(1, XXX));Typically, the postprocessor generates this code, but you can specify it yourself in the IPersistent.initializeContents() method. In any case, the call to handle.getClassField() returns an UnregisteredType object. The cast to Pet is required because Pet is the type of the mypet instance variable. However, this cast does not work. You cannot cast an UnregisteredType to Pet because UnregisteredType is not Pet and is not a subclass of Pet. The Java VM throws a ClassCastException in the middle of the initialization. The Person object is never initialized.
Now suppose that the Pet class is registered, and that the Shoe class, which is the type of the Person.mytrash instance variable, is not registered. ObjectStore creates an UnregisteredType object and the handle.getClassField() method returns it:
mytrash = (Object)(handle.getClassField(1, XXX));This time, the cast works correctly because UnregisteredType is a subclass of Object. The initialization succeeds, and the application continues to run.
You cannot do very much with objects of type Object, so it is likely that the first thing you would do is try to cast the UnregisteredType object to some specific type that you expect it to be. However, this does not work. If you try to cast an UnregisteredType object to a type other than java.lang.Object or UnregisteredType, the Java VM throws ClassCastException.
Unfortunately, the ClassCastException does not identify the type that is unregistered. There are two ways that you can determine the name of the type that is not registered:
The disadvantage of this approach is that you must edit and recompile your code.
When ObjectStore determines that a type is not registered, it checks the setting of the COM.odi.trapUnregisteredType property. If the property is not set, the default, ObjectStore creates an UnregisteredType object to represent the unregistered type. If COM.odi.trapUnregisteredType is set, ObjectStore throws FatalApplicationException and provides a message that indicates the name of the class that is unregistered.
Foo foo = (Foo) db.getRoot("foo");
If the Foo class is unregistered, the Java VM throws a ClassCastException when it comes to the (Foo) cast operation. See the previous section for two ways to determine which class is unregistered in this situation.
However, when the value of a root is an unregistered type, it can mean that none of your persistence-capable types is registered. This is often true when an UnregisteredType object causes a ClassCastException very early in your program. Your best course of action is likely to be to ensure that your persistence-capable classes are in your CLASSPATH, rather than trying to determine which class is not registered.
Updated: 10/07/98 08:45:40