PSE/PSE Pro for Java API User Guide
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.
PSE/PSE Pro'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, PSE/PSE Pro finds all objects reachable from persistent roots and stores them in the database.
To store objects in a database,
- Open the database or create the database you want to store the objects in. Be sure the database is opened for update.
- Start an update transaction.
- Create a database root and specify that it refers to one of the objects you want to store.
- Ensure that any other objects you want to store are referenced by the object that the database root refers to.
- Commit the transaction. This stores the object that the database root refers to and any objects that that object references.
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.
- If there are other objects that you want to store in the same segment, make those objects reachable from the migrated object.
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.
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, PSE/PSE Pro makes it persistent automatically when the transaction commits. PSE/PSE Pro migrates it to the default segment, which is the segment returned by Database.getDefaultSegment().
For example, suppose you create the variable db to be a handle to a database and an object called anObject, and start a transaction. The line below 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. When the application commits the transaction, PSE/PSE Pro stores anObject and any reachable objects in the database referred to by db, if they are not already there. If the referenced object references any transient objects that are not persistence-capable, and you try to commit the transaction, PSE/PSE Pro throws ObjectNotPersistenceCapableException.
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.
After you create one or more database roots, you can 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 persistent objects.
Creating too many roots can cause performance problems. The maximum practical number of roots is approximately 100.
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 on.
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.
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, PSE/PSE Pro stores it in the default segment of the database. The default segment is the segment returned by Database.getDefaultSegment(). The signature is
public void setRoot (String name, Object object)
If PSE/PSE Pro cannot find the specified root, DatabaseRootNotFoundException is thrown.
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 PSE/PSE Pro cannot find the specified root, it throws DatabaseRootNotFoundException. The 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. Note that there is presently no Java garbage collection of objects in the database. If a value of a root is no longer referenced after the root is destroyed, the object becomes unreachable. You should invoke ObjectStore.destroy() on it.
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.
Follow these steps to retrieve a persistent object from a database:
- Open the database.
- Start a transaction. If you want to modify the object, start an update transaction.
- Call the Database.getRoot() method on the database and specify the name of a previously created root. PSE/PSE Pro returns a reference to the object that the root refers to.
- Access the object just as you would access a transient object.
If you do not plan to run the postprocessor, see Chapter 11, Manually Generating Persistence-Capable Classes, Making Object Contents Accessible.
Calling the getRoot() Method
The signature for the getRoot() method is
public Object getRoot(String name)
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, PSE/PSE Pro throws DatabaseRootNotFoundException.
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()
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.
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.
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 one and 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 reverts to the last committed copy.
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. In other words, if you retrieve the same object through different paths, == returns true because PSE/PSE Pro preserves object identity.
If you are using the JDK 1.1, and if the system property COM.odi.disableWeakLinks 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. 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 PSE/PSE Pro object table, then the object can be garbage collected. (The object in the database, of course, is not garbage collected.)
When you commit a transaction, PSE/PSE Pro
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)
The values you can specify for retain are:
Making Persistent Objects Stale
Call the commit() method with no argument to make all persistent objects stale. Stale persistent objects are not accessible and their contents are set to default values. If your Java program still has references to these objects, any attempt to use those references (such as by accessing a field or calling a method on the object) causes PSE/PSE Pro to throw ObjectException. Therefore, your application must discard any references to persistent objects when it calls this overloading of commit().
This overloading of commit() also discards any internal PSE/PSE Pro references to the copies of the objects in your Java program. This makes the following objects available for garbage collection by the Java VM:
Stale persistent objects are not available for Java garbage collection if your Java application has transient references to them.
You can reaccess the same objects in the database in subsequent transactions. To do so, you must look up a database root and traverse to objects from there. PSE/PSE Pro creates new copies of the objects in the Java VM.
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. The disadvantage is that any references to these objects that your Java program holds become unusable. In general, invoking Transaction.commit(retain) is a safer approach.
Invoking commit(ObjectStore.RETAIN_STALE) is identical to calling commit() with no argument.
Call commit(ObjectStore.RETAIN_HOLLOW) to make persistent objects (the copies of the objects in your Java program) hollow. PSE/PSE Pro 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 reaccessed in a subsequent transaction, PSE/PSE Pro changes the contents of the copy of the object in your Java program to contain the contents of the object in the database.
An application cannot access hollow objects outside a transaction. An attempt to do so causes PSE/PSE Pro to throw NoTransactionInProgressException.
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. The disadvantage of retaining persistent objects as hollow objects is that in Java VM implementations for which PSE/PSE Pro does not have weak reference support (all non-JDK 1.1 based VMs), hollow persistent objects are not available for Java garbage collection. This is true regardless of whether or not your Java program has references to these objects.
Sometimes an application might retain a reference to an object and so prevent Java garbage collection that would otherwise occur. It is good practice to avoid retaining references to objects unnecessarily.
Call commit(ObjectStore.RETAIN_READONLY) to retain the copies of the objects in your Java program as readable persistent objects. PSE/PSE Pro maintains the contents of the persistent objects as they are at the end of the transaction.
After this transaction and before the next transaction, your application can read the contents of the persistent objects as they were the last time the objects were used in a transaction. 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 PSE/PSE Pro to throw NoTransactionInProgressException.
In a subsequent transaction, any cached references to persistent objects remain valid. If an object has changed on disk (because of a concurrent application updating the object) then PSE/PSE Pro recopies the object's contents from the database when it is accessed in a subsequent transaction.
The advantages of using commit(ObjectStore.RETAIN_READONLY) are
The disadvantage of using commit(ObjectStore.RETAIN_READONLY) is that fewer objects become available for Java garbage collection because the contents of the copies of the objects in your Java program are not cleared. Until you call commit() or commit(ObjectStore.RETAIN_STALE), this consumes more and more virtual memory. This affects performance and might result in the Java VM running out of virtual memory.
If you are using Java Remote Message Interface (RMI) or serialization, you can call the Persistent.deepFetch() method followed by commit(ObjectStore.RETAIN_READONLY). This allows you to perform object serialization outside a transaction.
Call commit(ObjectStore.RETAIN_UPDATE) to retain the copies of the objects in your Java program as readable and writable. PSE/PSE Pro maintains the contents of the persistent objects as they are at the end of the transaction.
This is exactly like specifying 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, PSE/PSE Pro discards any updates made to the objects between transactions.
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 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. When you evict an object, PSE/PSE Pro
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)
The remainder of this section discusses the state of the evicted object after the eviction.
When you invoke evict(Object), PSE/PSE Pro makes the evicted object stale. PSE/PSE Pro 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.
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 from other objects in the Java program). The disadvantage is that any references to the evicted object become stale. If you try to use the stale references, PSE/PSE Pro 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.
A call to evict(Object, ObjectStore.RETAIN_STALE) is identical to a call to evict(Object).
When you invoke evict(Object, ObjectStore.RETAIN_HOLLOW) PSE/PSE Pro makes the evicted object hollow. PSE/PSE Pro 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, PSE/PSE Pro 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.
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). The reason not to use this method is that in Java VM implementations for which PSE/PSE Pro does not have weak reference support (all non-JDK 1.1 based VMs), hollow persistent objects are not available for Java garbage collection.
Sometimes an application might retain references to an object and so prevent Java garbage collection that would otherwise occur. It is good practice to avoid retaining references to objects unnecessarily.
When you invoke evict(Object, ObjectStore.RETAIN_READONLY) PSE/PSE Pro
Your application can read or modify the evicted object in the same transaction. If it does, PSE/PSE Pro does not have to recopy the contents of the object from the database to your program.
Any changes to the evicted object are already saved in the database. (Of course, if the transaction aborts, the changes are rolled back.) Therefore, if the evicted object is not referenced from other objects in the Java program, it becomes available for Java garbage collection.
The advantage of using evict(Object, ObjectStore.RETAIN_READONLY) is that the object becomes available for Java garbage collection on platforms that support weak references.
You must ensure that a persistent object never refers to a persistent object that belongs to a different session. PSE/PSE Pro throws an exception if it reaches a persistent object belonging to one session while it is performing transitive persistence for another session.
After the transaction, a noncooperating thread, as well as a cooperating thread or a thread that does not belong to a session, can access persistent objects for which you specify RETAIN_READONLY or RETAIN_UPDATE. However, if any thread tries to follow a reference to an object that was not already accessed, PSE/PSE Pro throws ObjectStoreException to indicate that the object does not belong to the session.
Suppose you access the OA object from a thread that belongs to session A. You commit the transaction in which you accessed OA and you specify commit(RETAIN_READONLY). From any thread that belongs to any session, you can then use OA in any way that is allowed when
You can read OA. You cannot read in objects that are referenced by OA but not already available. It does not matter whether or not some other thread has a transaction in progress.
You can also evict all persistent objects associated with the current session 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, PSE/PSE Pro 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, PSE/PSE Pro applies it to all active persistent objects that belong to the same session as the active thread.
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 throws ObjectException.
If you made any changes to the objects you evict outside a transaction, PSE/PSE Pro discards these changes at the start of the next transaction. They are not saved in the database.
Outside a transaction, eviction of an object has meaning only if you retained objects when you committed the previous transaction.
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 so 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.
In short, 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.
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.
In unusual circumstances, you might choose to manually annotate your classes instead of running the postprocessor. If this is the case, then for your application to access or update an object it must call the Persistent.fetch() or Persistent.dirty() method for each object it accesses or modifies.
If you plan to run the postprocessor on your code, any required calls to Persistent.fetch() or Persistent.dirty() are automatically inserted for you. If you do not plan to run the postprocessor, you must insert the calls explicitly.
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 states of transient objects. Any form of output that occurred before the abort cannot be undone.
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.
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 example below 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.
Destroying Objects in the Database
PSE/PSE Pro does not provide a garbage collector for objects in databases. You must explicitly destroy any object that you want to be deleted from persistent storage.
To destroy an object, call ObjectStore.destroy(). The 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.
After you destroy an object, you cannot access any of its fields. If you try to dereference a destroyed object, PSE/PSE Pro throws ObjectNotFoundException.
By default, when you destroy an object, PSE/PSE Pro does not destroy objects that the destroyed object references.
There is a hook method, Persistent.preDestroyPersistent(), that you can define. PSE/PSE Pro 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.
COM.odi.util.OSHashtable and COM.odi.util.OSVector define their own preDestroyPersistent() methods. If you call ObjectStore.destroy() on an instance of one of these classes, PSE/PSE Pro automatically destroys the underlying structures for these objects. Alternatively, you can call OSHashtable.destroy() or OSVector.destroy().
The usual practice is to remove references to a persistent object before you destroy that persistent object. PSE/PSE Pro throws an exception 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, PSE/PSE Pro throws an exception when you try to use that reference. This might occur long after the referenced object was destroyed.
A call to destroy() has no effect when the object being destroyed is an instance of one of the primitive wrapper classes listed below. All references to destroyed instances of these wrapper classes continue to have the value of the destroyed object.
String class
A call to destroy on a String object or a Double or Long primitive wrapper object also has a different behavior. When you dereference a reference to such a destroyed object PSE/PSE Pro 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.
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, PSE/PSE Pro tries to detect it and throw ObjectException. PSE/PSE Pro can throw ObjectException for objects that are instances of subclasses of the Persistent class.
Unless you manually annotate your classes to make them persistence-capable, you do not write Persistent.fetch() or Persistent.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.
[previous] [next]
doc@odi.com
Copyright © 1997 Object Design, Inc. All rights
reserved.
Updated: 05/13/97 12:18:09