ObjectStore Java API User Guide
Chapter 6

Storing, Retrieving, and Updating Objects

This chapter provides information about how to store data in a database and also how to read it back and update it. An application can access persistent data only inside a transaction and only when the database is open.

This chapter discusses the following topics:

Storing Objects

Working with Database Roots

Troubleshooting OutOfMemoryError

Retrieving Persistent Objects

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

Avoiding finalize() Methods

Troubleshooting Access to Persistent Objects

Handling Unregistered Types

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.

To store objects in a database, do the following:

  1. Open the database or create the database in which you want to store the objects. Be sure the database is opened for update.

  2. Start an update transaction.

  3. Create a database root or access an existing database root and specify that it refers to one of the objects you want to store.

  4. Ensure that any other objects you want to store are referenced by the object to which the database root refers.

  5. Commit the transaction. This stores the object that the database root refers to and any objects that object references.

In general, you should not create a root for each object you want to store in a database. You must create a root to store some object in a database by which all other objects can ultimately be reached.

How Objects Become Persistent

Objects can become persistent in several ways:

Storing Objects in a Particular Segment

When you want to store objects in a particular segment that is not the default segment, follow these steps:

  1. Open a database and start a transaction.

  2. If you have not already created the segment in which you want to store the objects, create the segment with the Database.createSegment() method.

  3. Invoke ObjectStore.migrate() to store an object and specify the segment in which you want to store the object.

  4. If there are other transient objects that you want to store in the same segment, make those objects reachable from the migrated object.

  5. Commit the transaction.

    ObjectStore stores all transient objects that are reachable from the migrated object in the same segment as the migrated object.

To allow access to the objects, you must also assign some objects to database roots.

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.

To avoid this situation, you must explicitly migrate transient objects to a database segment when those transient objects are referred to by objects in more than one segment. You must do this before the application commits the transaction and you must export the objects when you migrate them.

When a transaction commits, you must ensure that any transient objects that are reachable from persistent objects are persistence-capable. If one such object is not, ObjectStore throws AbortException, aborts the transaction, and informs you that a reachable object is not persistence-capable.

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.

This section discusses the following topics:

Creating Database Roots

When you create a database root, you give it a name and you assign an object to it. The database root refers to that object and the application can use the root name to access that object. In other words, the object that you assign to a root is the value of that root. The database root and the object assigned to the root are two distinct objects.

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().

More than one root can reference the same object; an object can be associated with more than one root. For example:

db.createRoot("Root1", anObject);
db.createRoot("Root2", anObject);

Example

For example, suppose you create the variable db to be a handle to a database opened for update and an object called anObject, and start an update transaction. The following line creates a database root:

db.createRoot("MyRootName", anObject);

Results

In the database referred to by db, this creates a database root named "MyRootName" and specifies that it refers to anObject. ObjectStore immediately stores anObject in the database referred to by db. When the transaction commits, ObjectStore stores in the database referred to by db any objects that are reachable from anObject, if they are not already in the database. If anObject or any object it references refers to any transient objects that are not persistence-capable, and you try to commit the transaction, ObjectStore throws ObjectNotPersistenceCapableException.

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.

Roots with Null Values

It is possible to create a root with a null value. This is useful for creating roots in preparation for assigning objects to them later.

Using Primitive Values as Roots

If you want to store a primitive value as an independent persistent object, such as the value of a root, use an instance of a wrapper class, such as an Integer. For example:

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.

Changing the Object Referred To by a Database Root

After you create a database root, you can change the object that it refers to. Inside an update transaction, call the Database.setRoot() method on the database that contains the root. You must specify an existing root and you can specify either a transient (but persistence-capable) or a persistent object. If you specify a transient object, ObjectStore immediately stores it in the default segment of the database. The default segment is the segment returned by the Database.getDefaultSegment() method. The method signature for changing the object associated with a root is

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);

How Many Roots Are Needed in a Database?

It is important to realize that you need not create a root for most objects that you want to store in a database. You only need to create roots for top-level objects that you want to look up by name. You must have at least one root to be able to navigate through a database. Without a root, you have no way of accessing the objects in the database.

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.

Troubleshooting OutOfMemoryError

If you are storing many large objects, it might appear as though they are never garbage collected. In fact, you might receive the java.lang.OutOfMemoryError. Here is a discussion of why this might happen and what you can do.

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:

If you want the storage for a large object to be reclaimed immediately, wrap the large object in a small dummy object. Export the dummy object instead of the large object. This causes the small object to be retained as a hollow object that is referred to with a weak reference. Upon transaction commit with ObjectSTore.RETAIN_STALE or ObjectStore.RETAIN.HOLLOW, the large object is available to be garbage collected almost immediately.

Retrieving Persistent Objects

To read the contents of objects in a database, you must first obtain the value of an existing database root. Then the application makes the contents of the referenced object and any object it references accessible.

This section discusses the following topics:

Steps for Retrieving Persistent Objects

Follow these steps to retrieve a persistent object from a database:

  1. Open the database.

  2. Start a transaction. If you want to modify the object, start an update transaction.

  3. Call the Database.getRoot() method on the database and specify the name of a previously created root.

    ObjectStore returns a reference to the object that the root refers to. This assumes that the object you want is either the object the root refers to or reachable from that object.

  4. Access the object just as you would access a transient object.

    If you do not plan to run the postprocessor, see Chapter 9, Manually Generating Persistence-Capable Classes, Making Object Contents Accessible.

Obtaining a Database Root

Call the Database.getRoot() method to obtain a database root. When you obtain a database root, it returns a reference to its assigned object. You can use this reference to obtain a reference to any object that the assigned object references.

The signature for the getRoot() method is

public Object getRoot(String name) 

Null return

It is possible for the getRoot() method to return a null value, which indicates that there is no object associated with the root. It does not mean that the root does not exist. If the root does not exist, ObjectStore throws DatabaseRootNotFoundException.

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()

Determining Which Database Contains an Object

You can use the Database.of() method to determine the database in which an object is stored. The method signature is

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.

Determining Whether an Object Has Been Stored

To determine whether an object has already been stored in a database, call the ObjectStore.isPersistent() method. The method signature is

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.

Iterating Through the Objects in a Segment

To obtain an enumeration of the objects in a segment, call the Segment.getObjects() method. This allows you to access any objects that are unreachable, but which have not yet been garbage collected. It also provides an application-independent means for processing all objects within a segment. The method signature is

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.

After you create a SegmentObjectEnumeration, objects in the enumeration might get destroyed. When you use the enumeration to iterate through the objects, ObjectStore skips any destroyed objects. However, if you destroy a COM.odi.coll dictionary from C++, ObjectStore does not recognize that the dictionary has been destroyed. In this case, the enumeration might return a reference to garbage.

You can use a SegmentObjectEnumeration across transactions. If a transaction in which you use the SegmentObjectEnumeration aborts, the enumeration becomes stale.

After you create an enumeration of the objects in a segment, other sessions are blocked from destroying that segment until you end your transaction. If you create the enumeration and then destroy the segment, the next call to nextElement() or hasMoreElements() causes ObjectStore to throw SegmentNotFoundException.

There is a bug that makes the enumeration work incorrectly if you call Transaction.checkpoint(RETAIN_STALE). Doing so causes the next use of the enumeration to throw ObjectException because of stale objects. This will be fixed in a future release.

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).

ObjectStore provides the ExternalReference class to represent external references. To help you use external references, this section discusses

Creating External References

When you have a persistent object for which you want to create an external reference, follow these steps:

  1. Create a new ExternalReference object by passing the persistent object to the constructor.

  2. Obtain the database, segment ID, and location of the persistent object for which you just created an ExternalReference object. Use ExternalReference methods to do this.

  3. In the format of your choice, store the values for the database, segment ID, and location wherever you want them to be.

    For example, you can call the Database.getPath() method on the database reference and store a string pathname. Alternatively, you can look up the database object in a hash table, obtain an identifier, and store the identifier. Such an identifier is typically shorter than a pathname.

Example

For example:

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.

Requirements

If you want to create an external reference for an object that becomes persistent when the transaction commits, you must migrate the object to the database before you can create the external reference.

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.

One-argument constructor

A transaction must be in progress to use the one-argument external reference constructor, whether or not the specified persistent object is null. The constructor signature is

public ExternalReference(Object o)

Three-argument constructor

An open transaction is not required for the three-argument constructor. This constructor signature is

public ExternalReference(Database d, int segid, int loc)

Cloning

The ExternalReference class implements the Cloneable interface. This allows you to call the clone() method on an ExternalReference object if you want to create another ExternalReference object with the same contents.

Using the No-Arguments Constructor

For convenience, the ExternalReference class also has a no-arguments constructor that sets the external reference to represent null. When an ExternalReference object represents null,

When you try to call ExternalReference.getObject() or ExternalReference.toString() on an external reference object that was created with the no-arguments constructor, the object must contain all these values. If ObjectStore finds one of them but not the others, it throws IllegalArgumentException. You must be in a transaction when you use the no-arguments constructor.

Caution About Creating External References to Nonexported Objects

When you create an external reference to an object that is not exported, you must be aware of these possible situations:

If one of these situations occurs and you try to use the external reference, you receive incorrect results.

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.

Obtaining Objects from External References

To obtain the object that an external reference refers to, you must do the following:

  1. Set an ExternalReference object with the values of your external reference.

  2. Use the ExternalReference.getObject() method to obtain the object.

Perform these steps while the database is open and a transaction is in progress.

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(
refToDb, segId, loc);
To obtain the corresponding object, call ExternalReference.getObject(). For example:

Object myPObj = myExRef.getObject();

Determining Whether Two External References Refer to the Same Object

Two external references are considered to be equal if they both refer to the same object. In other words, if you call ExternalReference.getObject() on each external reference, both calls return identical objects. Call the ExternalReference.equals() method to determine whether two external references refer to the same object. The method signature is

public boolean equals(Object obj)

Reusing External Reference Objects

After you create an ExternalReference object, you can reuse it any number of times. The advantage of reusing ExternalReference objects is that you avoid the overhead of storage allocation and garbage collection when you use large numbers of external references.

Storing external references

You can use an existing ExternalReference object to store an external reference. To do this, set the ExternalReference object to contain information about the persistent object for which you want an external reference. A transaction must be in progress. The method signature is

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.

Obtaining externally referenced objects

You can also reuse an external reference object when you want to obtain a persistent object from an externally stored reference. Use the ExternalReference.set xxx() methods. The method signatures are

For example, suppose you have an external reference to hisPObj. You can reuse the myExRef object to obtain hisPObj by passing the stored values for hisPObj to the set methods on myExRef:

myExRef.setDatabase(refToDb);
myExRef.setSegmentId(segId);
myExRef.setLocation(loc);
hisPObj = myExRef.getObject();

Encoding External References as Strings

The ExternalReference.toString() and ExternalReference.fromString() methods act as a printer and parser, respectively. They allow you to encode an ExternalReference as a string, and then parse the string to rebuild an equivalent ExternalReference. This is convenient to use, but the strings are relatively long. They include the entire pathname of the database.

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()

fromString()

To use the fromString() method, pass it a string created by a call to toString(). This creates and returns a new ExternalReference object. The method signature is

public static ExternalReference fromString(String string)

Large numbers of external references

There might be situations when you print a large number of ExternalReference objects and you know that they are all from the same database. In this case it is more efficient to construct your own printed representation by using the ExternalReference.getSegmentId() and ExternalReference.getLocation() methods.

External References and Transactions

You must be in a transaction when you call these methods and constructors on the ExternalReference class:

You do not need to be in a transaction to call the other ExternalReference constructor and methods.

Updating Objects in the Database

To update objects in the database, start at a database root and traverse objects to locate the objects you want to modify. Make your modifications by updating fields or invoking methods on the object, just as you would operate on a transient object. Finally, save your changes by committing the transaction (this ends the transaction) or evicting the modified objects (this allows the transaction to remain active).

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.

Background for Specifying Object State

When a Java program accesses an object in an ObjectStore database, there are two copies of the object:

Normally, you need not be aware of the fact that there are two copies. Your application simply operates on the object in the Java program as if that is the only copy. This is why the documentation refers to this copy as a persistent object. However, the fact that there are two copies becomes apparent if a transaction aborts. In this case, the contents of the object in the database revert to the last committed copy.

About Object Identity

In a session, persistent objects maintain identity. Suppose there is an object in the database that is referred to by two different objects. You can reach the object in the database through two navigation paths. Regardless of which path you use, the resulting persistent object is the same object in the Java VM. In other words, if you have two unrelated objects (a and b), that refer to a third object (c), a.c == b.c is true.

In a single session, the Java VM never creates two distinct objects that both represent the same object in the database.

Sample class definitions

For example, suppose you have the following classes:

public class City {
      String name;
      int population;
}
public class State {
      City capital;
      String name;
      int population;
}

Creating objects

Suppose you also have the following code, which creates some instances of these classes and stores them:

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

Accessing stored objects

Now you execute the following code to access the stored objects:

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");

Results

This code prints "same". This is because boston1 and boston2, even though they are located through different paths in the database, are still represented by the same object in the Java VM and therefore they are ==.

If you use cities to reach boston1 and you modify boston1, you can then use states to access the updated version as boston2.

Alternative way to create objects

However, suppose that you change the way you create some instances of the City and State classes. Instead of doing it in one transaction, use two transactions. Create a new city called Boston, store it in the cities hash table, and commit the transaction. In a subsequent transaction, define a new state, massachusetts, and assign yet another new Boston object to its capital field. Store massachusetts in the states hash table and commit the transaction. Here is the code that does this:

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();

Results of alternative approach

Now there are two different Boston objects in the database. One is accessible through the cities root and another one is accessible through the states root. Although both objects are instances of City and both are named Boston, they are two different objects because they were stored as two different objects. When you create objects with separate calls to new, the objects can never be the same in the sense of ==.

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.

Strings and primitive wrappers

There are additional considerations for Strings and primitive wrapper classes.

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();
      }

About the Object Table

ObjectStore keeps a table of all objects referenced in a transaction. If you refer to the same object in the database twice (perhaps accessing the object through different paths), there is only one copy of the object in your Java program. Remember, if you retrieve the same object through different paths, == returns true because ObjectStore preserves object identity.

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.)

Committing Transactions to Save Modifications

When you commit a transaction, ObjectStore

Method signatures

The commit() method has two overloadings. The first overloading takes no argument. The method signature is

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)

Contents

This section discusses the object states in the following topics:

Exceptions

If you try to commit a transaction when an object in one segment refers to an unexported object in another segment, ObjectStore throws AbortException and aborts the transaction.

Incorrect access to persistent objects

If your application commits a transaction while an annotated method is executing, your program might incorrectly access additional persistent objects after the commit. For more information about this and a work around, see Troubleshooting Access to Persistent Objects.

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.

Stale persistent objects are not available for Java garbage collection if your Java application has transient references to them.

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.

Advantage

The advantage of using commit() with no argument is that it wipes your database cache clean, and typically makes all transient copies of persistent data available for Java garbage collection.

Disadvantage

The disadvantage is that any references to these objects that your Java program holds become unusable.

Alternative method

Invoking commit(ObjectStore.RETAIN_STALE) is the same as calling commit() with no argument.

Making Persistent Objects Hollow

Call commit(ObjectStore.RETAIN_HOLLOW) to make persistent objects (the copies of the objects in your Java program) hollow. ObjectStore resets the contents of persistent objects to default values.

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.

Outside transaction

An application cannot access hollow objects outside a transaction. An attempt to do so causes ObjectStore to throw NoTransactionInProgressException.

Advantage

The advantage of invoking commit(ObjectStore.RETAIN_HOLLOW) is that any references to persistent objects that the Java application holds remain valid in subsequent transactions. This means that it is not necessary to renavigate to these objects from a database root.

Garbage collection

Sometimes an application might retain a reference to an object and prevent Java garbage collection that would otherwise occur. It is good practice to avoid retaining references to objects unnecessarily.

Scope

If you commit a transaction with ObjectStore.RETAIN_HOLLOW, and then commit a subsequent transaction with no retain argument or ObjectStore.RETAIN_STALE, this cancels the previous ObjectStore.RETAIN_HOLLOW specification. No object references are available in the next transaction. This is true regardless of whether or not they were previously retained.

Retaining Persistent Objects as Readable

Call commit(ObjectStore.RETAIN_READONLY) to retain the copies of the objects in your Java program as readable persistent objects. ObjectStore maintains the contents of the persistent objects that the application read in the transaction just committed. The contents of these persistent objects are as they were the last time the objects were read or modified in the transaction just committed.

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.

Scope

If you commit a transaction with ObjectStore.RETAIN_READONLY, the contents of only those persistent objects whose contents were accessed in the transaction just committed are available to you after the transaction. This is because ObjectStore makes all retained objects hollow at the start of the next transaction. Any cached references to persistent objects remain valid. In the new transaction, ObjectStore fetches the contents of a persistent object when your application requires it.

Advantage

The advantage of using commit(ObjectStore.RETAIN_READONLY) is that the copies of the persistent objects in your Java program remain accessible after the transaction is over. In the next transaction, any cached references to persistent objects remain valid. ObjectStore copies the object's contents from the database when you access the object.

Disadvantage

The disadvantage of using commit(ObjectStore.RETAIN_READONLY) is that it makes more work for the Java garbage collector, because the contents of the copies of the objects in your Java program are not cleared.

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.

Serialization

If you are using Java Remote Method Interface (RMI) or serialization, you can call the ObjectStore.deepFetch() method followed by commit(ObjectStore.RETAIN_READONLY). This allows you to perform object serialization outside a transaction.

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.

In a transaction, you might read the contents of an object, but not the contents of objects the first object refers to. For example, during a transaction, suppose you access a vector but not any of the elements in the vector. When you commit the transaction, the contents of vector elements are not available in the transaction and they are not retained. In other words, to be able to read the contents of an object between transactions, you must read that particular object during the previous transaction.

To be able to read objects between transactions, you might want to call ObjectStore.deepFetch() on an object. This method fetches the contents of the specified object, the contents of any objects that object refers to, the contents of any objects those objects refer to, and so on for all reachable objects.

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.

Here is another situation in which you would receive the NoTransactionInProgressException:

  1. In a transaction, you read object A.

  2. You commit the transaction with ObjectStore.RETAIN_READONLY.

  3. You start a new transaction. It does not matter whether or not you access object A in this transaction.

  4. You commit this transaction with ObjectStore.RETAIN_STALE or without a retain argument.

  5. Outside a transaction, you try to access object A and you receive the NoTransactionInProgressException.

You might think that because you retained A after a previous transaction, its contents are still available. This is not the case. Since nothing was retained after the second transaction, the contents of A are no longer available.

MVCC alternative

Many applications that seem to be suitable for retaining persistent objects as readable can be better implemented with the Multiversion Concurrency Control (MVCC) feature.

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.

A specification of ObjectStore.RETAIN_UPDATE is exactly like a specification of ObjectStore.RETAIN_READONLY, except that if your application accesses the objects after the transaction and before the next transaction, it can modify as well as read the objects. At the beginning of the next transaction, ObjectStore discards any updates made to the persistent objects between transactions. ObjectStore does not modify the contents of any transient-only fields unless you have explicitly defined one of the following methods to modify transient-only fields.

The clearContents() and initializeContents() methods that are generated by the postprocessor do not modify transient-only fields.

Advantage

The advantage of using commit(ObjectStore.RETAIN_UPDATE) is that the copies of the objects in your Java program become scratch space between transactions. You can use them to determine what the results might be for a particular scenario.

Disadvantage

The disadvantage is that updates are automatically discarded at the beginning of the next transaction. This can make it difficult to debug applications that use this option indiscriminantly.

Caution About Retaining Nonexported Objects

Suppose you commit a transaction and retain persistent objects as hollow, readable, or writable. Retained objects that are not exported can become stale inside a subsequent transaction. This can happen if you run the schema evolution tool or use the schema evolution API. If you try to access a retained object that has become stale after schema evolution, ObjectStore throws UnexportedObjectsBecameStaleException.

When retained objects are exported, ObjectStore can correctly retain them after schema evolution.

Consequently, you must follow at least one of these rules:

Evicting Objects to Save Modifications

You might want to save modifications to an object or change the state of an object without committing a transaction. The evict() method allows you to do this.

Method signatures

The evict() method has two overloadings. The first overloading takes an object as an argument. The method signature is

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)

Contents

This section provides the following information about evicting objects:

Description of Eviction Operation

When you evict an object, ObjectStore

References to other objects

When you evict an object, ObjectStore does not evict objects that the evicted object references.

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.

Caution

If your application evicts one or more objects while an annotated method is executing, your program might incorrectly access persistent objects after the eviction. For more information about this and a work around, see Troubleshooting Access to Persistent Objects.

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.

The effect on accessibility to the copy of the object in your Java program is therefore similar to the effect of commit() and commit(ObjectStore.RETAIN_STALE). However, if the transaction aborts, any changes to the evicted object are discarded.

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.

If the application accesses the evicted object in the same transaction, ObjectStore copies the contents of the object from the database to the copy in your Java program. If your application modified the object before evicting it, these modifications are included in the new copy in your Java program.

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).

Sometimes an application might retain references to an object and prevent Java garbage collection that would otherwise occur. It is good practice to avoid retaining references to objects unnecessarily.

Setting the Evicted Object to Remain Active

When you invoke evict(Object, ObjectStore.RETAIN_READONLY), ObjectStore

Garbage collection

Any changes that were made before the object was evicted are saved in the database. (Of course, if the transaction aborts, the changes are rolled back.) Therefore, if the evicted object is not referenced by other objects in the Java program, it becomes available for Java garbage collection.

Additional changes

Your application can read or modify the evicted object in the same transaction. If it does, ObjectStore does not have to recopy the contents of the object from the database to your program. When the application commits the transaction or evicts the object again, ObjectStore saves in the database any new changes to the evicted object.

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.

Advantage

The advantage of using evict(Object, ObjectStore.RETAIN_READONLY) is that the updated object becomes available for Java garbage collection.

Summary of Eviction Results for Various Object States

The following table shows the results of an eviction according to the value specified for the retain argument.

Results of Eviction RETAIN_STALE RETAIN_HOLLOW RETAIN_READONLY
Object state Stale Hollow Active
References to evicted object Stale Remain valid Remain valid
Candidate for Java
garbage collection
Candidate Can be candidate Not a candidate

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

If you specify RETAIN_STALE or RETAIN_HOLLOW, ObjectStore applies it to all persistent objects that belong to the same session as the active thread. It does it in the same way that it applies it to one object for the evict(object, retain) method. If you specify RETAIN_READONLY, ObjectStore applies it to all active persistent objects that belong to the same session as the active thread.

Evicting Objects When There Are Cooperating Threads

Before an application evicts an object, it must ensure that no other thread requires that object to be accessible. For example, suppose you have code like this:

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.

Evicting Objects Outside a Transaction

Outside a transaction, eviction of an object has meaning only if you retained objects when you committed the previous transaction. In other words, if you invoke the commit(retain) method and specify a value for the retain argument other than RETAIN_STALE, you can evict retained objects outside a transaction.

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.

Aborting Transactions to Cancel Changes

If you modify some objects and then decide that you do not want to keep the changes, you can abort the transaction. Aborting a transaction

Only the state of the database is rolled back. The state of transient objects is not undone automatically. Applications are responsible for undoing the state of transient objects. Any form of output that occurred before the abort cannot be undone.

This section discusses the following topics:

Caution

If your application aborts a transaction while an annotated method is executing, your program might incorrectly access additional persistent objects after the abort operation. For more information about this and a work around, see Troubleshooting Access to Persistent Objects.

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();

Setting the Default Abort Retain State

Call the setDefaultAbortRetain() method to set the default state for persistent objects after a transaction is aborted. The method signature is

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.

Specifying a Particular State for Persistent Objects

To abort a transaction and specify a particular state for persistent objects after the transaction, call the abort(retain) method on the transaction. The method signature is

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.

RETAIN_STALE

ObjectStore.RETAIN_STALE resets the contents of all persistent objects to their default values and makes them stale. This is the same as calling abort() when Transaction.setDefaultAbortRetain() has either not been called or been called with ObjectStore.RETAIN_STALE as its argument.

RETAIN_HOLLOW

ObjectStore.RETAIN_HOLLOW resets the contents of all persistent objects to their default values and makes them hollow. In the next transaction, you can use references to persistent objects from this transaction.

RETAIN_READONLY

ObjectStore.RETAIN_READONLY retains the contents of unmodified persistent objects that were read during the aborted transaction. Any objects that were modified become hollow objects, as if ObjectStore.RETAIN_HOLLOW had been specified. Objects whose contents were read but not modified in the aborted transaction can be read after the aborted transaction.

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.

RETAIN_UPDATE

ObjectStore.RETAIN_UPDATE retains the contents of persistent objects that were read or modified during the aborted transaction. The values that are retained are the last values that the objects contained before the transaction was aborted. Even though the changes to the modified objects are undone with regard to the database, the changes are not undone in the objects in the Java VM.

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.

Destroying Objects in the Database

You can explicitly destroy any object that you want to be deleted from persistent storage. You can also perform persistent garbage collection, which destroys unreachable objects in the database. See Performing Garbage Collection in a Database. The discussion of the destroy operation covers the following topics:

Calling ObjectStore.destroy()

To destroy an object, call ObjectStore.destroy(). The method signature is

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.

There is a hook method, IPersistent.preDestroyPersistent(), that you can define. ObjectStore calls this method before actually destroying the specified object. This method is useful when an object has underlying structures that you want to destroy along with the object. The default implementation of this method does nothing.

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

For additional information about ObjectStore treatment of String instances, see Description of Special Behavior of String Literals. For example, if you define a class such as the one below, when you destroy an instance of this class, you should also explicitly destroy s and d.

class C {
      int i;
      String s;
      Double d;
}

Advantages of explicit destroy

You should always consider whether or not to have preDestroyPersistent() call ObjectStore.destroy() on fields that contain String types, instance of wrapper classes that have been explicitly migrated, or types that you define. The advantages of explicitly destroying objects are

Disadvantages of explicit destroy

The disadvantages of explicitly destroying such objects are

If you do not explicitly destroy an unreferenced object, ObjectStore destroys it when you run the persistent garbage collector.

You do not need to have preDestroyPersistent() call ObjectStore.destroy() on fields that contain primitive types.

Example

For example, suppose you have a persistence-capable class called MyVector that has a private field called contents. When an instance of MyVector is persistent, the contents field is also persistent, but a user would not have access to it because it is private. If a user calls ObjectStore.destroy() on an instance of MyVector, the operation destroys the instance but not the contents object.

If you are the programmer implementing the MyVector class, you have two choices:

Here is code that shows the second alternative:

public class MyVector {
      private Object[] contents;
      public addElement(Object o) {
            contents[nextElement++] = o;
      }
      public void preDestroyPersistent() {
            if (contents != null)
                  ObjectStore.destroy(contents);
      }
}

Destroying Objects That Are Referred to by Other Objects

The usual practice is to remove references to a persistent object before you destroy that persistent object. ObjectStore throws ObjectNotFoundException when you try to access a destroyed object. It is up to you to clean up any references to destroyed objects.

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.

String class

A call to destroy on a String object has different behavior. When you dereference a reference to such a destroyed object, ObjectStore does not throw ObjectNotFoundException. Instead, references to the destroyed object from objects modified in the same transaction as the destroy operation continue to have the value of the destroyed object. References to the destroyed object from objects not modified in the same transaction appear as null values when an object containing such a reference is fetched.

Hash tables

You should avoid having a hash table refer to a destroyed object. It is difficult to remove a reference from a hash table after you destroy the object that it refers to. This is because the search through the hash table for the referring object might cause ObjectStore to try to access the destroyed object. In fact, a search for another object in the hash table might cause ObjectStore to access the destroyed object. The result is that the hash table lookup procedure throws ObjectNotFoundException and the hash table becomes useless. Consequently, you should always remove objects from hash tables before you destroy them.

Default Effects of Various Methods on Object State

The table below summarizes the default effects of various methods on the state of hollow or active persistent objects. You should never try to invoke a method on a stale object. If you do, ObjectStore tries to detect it and throw ObjectException. ObjectStore can throw ObjectException for objects that are instances of classes that implement the IPersistent interface.

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 When Invoked on a Hollow or Active Object
ObjectStore.fetch() Active persistent object
ObjectStore.dirty() Active persistent object
ObjectStore.evict() Hollow persistent object
ObjectStore.destroy() Stale persistent object



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

See also Creating Persistence-Capable Classes with Transient Fields.

Behavior of Transient Fields

In a persistence-capable class, a field designated with the transient keyword behaves as follows:

Preventing fetch() and dirty() Calls on Transient Fields

When you run the postprocessor on a class that has transient fields, you might want to specify the -noannotatefield option for the transient fields. This option prevents access to the specified field from causing fetch() and dirty() calls on the containing object. This is useful for transient fields when you access them outside a transaction. Normally, access to a transient field causes fetch() or dirty() to be called to allow the postInitializeContents() and preFlushContents() methods to convert between persistent and transient states.

When you specify the -noannotatefield option, follow it with a qualified field name.

Background Information About Access to Transient Fields

Suppose you define class X with transient field Y. When you try to access X.Y outside a transaction, you receive NoTransactionInProgressException. This happens because the postprocessor annotates access to transient fields the same way that it annotates access to persistent fields, that is, with calls to fetch() and dirty() as needed. If you do not want this behavior, specify the -noannotatefield option followed by the qualified name of the transient field when you run the postprocessor.

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.

Avoiding finalize() Methods

Object Design strongly recommends that you do not define java.lang.Object.finalize() methods in application classes that are persistence-capable. If your persistence-capable class must define a finalize() method, you must ensure that the finalize() method does not access any persistent objects. This is because the Java garbage collector might call the finalize() method outside a transaction or from a thread that does not belong to the session of the object being finalized. Such a situation causes ObjectStore to throw ObjectStoreException and prevents execution of the finalize() method.

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.

Troubleshooting Access to Persistent Objects

Incorrect program behavior can happen when your program does one of the following while an annotated method is executing:

The general result is that your program might incorrectly access additional persistent objects after the abort, commit, checkpoint, or eviction. The specific results vary according to the retain setting ObjectStore uses for the operation:

The class file postprocessor (osjcfp) uses two optimizations that can cause incorrect access to persistent objects. Consequently, there are two options to the postprocessor that allow you to disable these optimizations:

Specify these options when you recognize the behavior described here.

Handling Unregistered Types

ObjectStore creates objects of type UnregisteredType when it must create a persistent object and it cannot find a ClassInfo subclass that describes the object it must create. The ClassInfo subclass might not be found because of a problem with the CLASSPATH or because the ClassInfo subclasses are not available for a particular database.

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.

If your application receives error messages that indicate unregistered types, the information here can help you determine what is happening and what to do about it. This section discusses

How Can There Be Unregistered Types?

How can there be a type in the database with no corresponding ClassInfo subclass? This can happen when

Can Applications Work When There Are Types Not Registered?

In some situations, it might not matter to your application that there is an object whose type is unregistered. For example, suppose you are looking up an element in a hash table. One of the elements in the hash table is of an unregistered type, but it is not the element you are looking for. Because ObjectStore creates an UnregisteredType object instead of throwing an exception, your application can keep running.

What Does ObjectStore Do About Unregistered Types?

ObjectStore provides the abstract class UnregisteredType to represent objects whose types are unregistered. When ObjectStore cannot find the ClassInfo subclass for a type that is referenced in your application, it

You can never read or modify an UnregisteredType object. Because of this, it is important for you to understand

With this information, you can determine whether your application can run with objects of unregistered types. Your application can continue to run as long as you do not try to read or modify an UnregisteredType object.

When Does ObjectStore Create UnregisteredType Objects?

ObjectStore creates an UnregisteredType object when it encounters an object in a database and it determines that the type of that object is not registered. ObjectStore encounters an object in a database when it

In the above list, initialize means to read the contents of the object out of the database and into the persistent Java object. This happens when ObjectStore calls IPersistent.initializeContents() and IPersistent.postInitializeContents().

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.

ClassInfo background

Typically, the postprocessor defines a ClassInfo subclass for your class. If you do not run the postprocessor, you must manually create the ClassInfo subclass. The name of the ClassInfo subclass is usually the name of the class with ClassInfo as a suffix. However, the suffix can also be Info or CI or another value that you specify with the -classinfosuffix postprocessor option.

Can Your Application Run with UnregisteredType Objects?

ObjectStore can use the UnregisteredType object if java.lang.Object is the type of the field in which the reference is being stored. For example, suppose you have the following class:

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.

Troubleshooting ClassCastExceptions Caused by Unregistered Types

If ObjectStore creates an UnregisteredType object and you do not try to do anything with it, your application should work just fine. Now suppose you try to do something with it. Since it exists, it must be in a variable of type java.lang.Object. (If it were not, you would have had trouble with it earlier, as in the Pet example in the previous section.)

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:

Changing your program

Somewhere in your program, you have a variable of type Object whose value is an object of the UnregisteredType class. Modify your program to cast this variable to type UnregisteredType. Then invoke the getTypeName() method on the UnregisteredType object. This returns the name of the type that is unregistered.

The disadvantage of this approach is that you must edit and recompile your code.

Setting the trapUnregisteredType property

ObjectStore provides the COM.odi.trapUnregisteredType property to help you determine which class is unregistered. The default is that this property is not set, and it is usually best to use the default.

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.

Advantage

The advantage of the COM.odi.trapUnregisteredType property is that it provides the name of the class that is unregistered.

Disadvantage

The disadvantage is that as soon as ObjectStore encounters the first object whose type is unregistered, your application stops running. If the object you want information about is the second object of an unregistered type that ObjectStore would encounter, ObjectStore never reaches that second object. When you set COM.odi.trapUnregisteredType, ObjectStore throws FatalApplicationException as soon as it encounters the first object whose type is unregistered.

Troubleshooting the Most Common Problem

There is a common situation in which an UnregisteredType object causes a ClassCastException. This is when you try to obtain a database root (Database.getRoot()) and the value of the root is an UnregisteredType object. For example:

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.



[previous] [next]

Copyright © 1998 Object Design, Inc. All rights reserved.

Updated: 10/07/98 08:45:40