) or as a bundled addition to Java development environments. BYTE reviewed ObjectStore 5.0 in the October 1997 issue (see "The Object Is to Manage Data"); in this column, I discuss Object Design's Java interface.
I examined the version of PSE bundled with Asymetrix's Supercede. Note that PSE's capabilities are essentially
a subset
of those found in PSE Pro and in the full-blown, multiuser ObjectStore.
PSE uses the concept of "persistence by reachability" (as does Poet). That is, an object is persistent if it's referenced by or contained in another persistent object. This naturally generates a kind of chicken-and-egg question: How does any object become persistent in the first place?
The answer is the
createRoot()
method. It creates a persistent object in the database and a
ssociates that object with a string. This is analogous to Poet's named objects. Once you have created a persistent root, it becomes a kind of anchor to which you can attach other objects. (Or the root could be a container storing persistent objects.)
A single database can hold an arbitrary number of roots. Furthermore, the referencing and containing can be nested arbitrarily deeply. If the root references an object that in turn references another object, and so on, the entire referenced chain of objects is accessible and therefore persistent.
Objects that can be stored in the PSE database are referred to as being "persistence-capable." Objects whose class includes methods that manipulate persistence-capable objects, but that themselves are not persistent, are called "persistence-aware" objects. All other objects are "transient objects."
To confuse you even further, a persistence-capable object can be in one of three states with regard to usability of the contents of its data members. A "holl
ow" object has default values in its data members. (For example, if you fetch an object from the PSE database, the objects that it references -- as yet unfetched -- are hollow.) Once you read a hollow object's contents from the database, the object becomes "active." Finally, if the object participates in an aborted transaction (I'll discuss transactions below), the object is regarded as "stale"; its contents should be treated as indeterminant.
In addition, an active object becomes "dirty" if, after its contents have been read from the database, any data member has been modified. Dirty objects are written back to the database when their enclosing transaction completes.
As implied above, all access to a PSE database occurs within the bounds of a transaction. Manipulating objects within a database follows this sequence: Begin a transaction, read and write persistent objects, and then close that transaction. New or updated objects are written to the database only if the transaction
closes successfully. If the transaction is aborted, the database remains in the state it was in prior to the beginning of the transaction.
Unique to PSE is how invisibility -- as far as the programmer is concerned -- does its work. Remarkably, you need not insert any explicit method calls to fetch and store persistent objects. Consequently, if you peruse the source code to a program that uses PSE, you'll see only calls to methods for opening and closing the database, and calls to methods for starting and committing transactions.
How, then, does anything get into or out of a database? This magic is wrought by PSE's postprocessor. When you build your PSE-enabled application, you need only bracket with transaction begin and end methods, stretches of code that operate on persistent objects. You compile your code and then run the resulting class files through the postprocessor.
When a persistent object is referenced, the postprocessor precedes the reference with a call to the
method to fetch that object. When a persistent object is modified, the postprocessor follows the code with a call that marks the object as "dirty." (In fact, the calls are named
fetch()
and
dirty()
.) The postprocessor also adds classes that can initialize the contents of fetched objects and write persistent objects' contents back into the database.
The postprocessor does the best job it can at deducing where to place calls to
fetch()
and
dirty()
. But sometimes it can pepper your code with more calls than are strictly necessary. A savvy programmer, knowing where those calls ought to go, can override the postprocessor and place
fetch()
and
dirty()
calls only where they must be (and thereby simultaneously reduce execution size and increase execution performance). The documentation includes guidelines that lead you through inserting explicit calls to
fetch()
and
dirty()
and exercising the final application to verify correctness of exec
ution.
One side effect of the postprocessor's modifying the class file is that debuggers can become slightly confused. Fortunately, because the Java class-file format associates source code line numbers with generated code, the postprocessor can make adjustments so that a debugger never gets completely befuddled. But sometimes, if you execute a "step into" on a statement that the postprocessor has -- in the class file -- inserted a
fetch()
or
dirty()
just after, you might find yourself suddenly stepping into PSE code. Executing "step out" solves the problem.
Although the full ObjectStore product supports indexes, PSE and PSE Pro don't. PSE has a persistent hash table that you can use to index on a collection of objects. But there's a catch if you want to generate a hash from a persistent PSE object: You must define such "persistent hashable" objects so that they descend from the persistent hashable superclass supplied with PSE.
The reason for this has t
o do with the fact that Java's default
hashcode()
method can be confused with PSE's internal goings-on. PSE creates invisible transient objects that are equivalent to persistent objects during transactions. The standard
hashcode()
method can return different hash values for the transient objects that represent the same persistent object in the database, potentially making a hash table unusable as an indexing scheme.
PSE's persistent hashable superclass includes a "hidden field" for holding a hashcode. PSE defines a new
hashcode()
method that reads that hidden field. When an instance of the object is created, PSE calculates and stores a hash value in that hidden field so that subsequent calls to
hashcode()
return the same hash value.
Although I might have made PSE's near-invisible interface sound unconventional, the latest releases of PSE and ObjectStore support the Java binding as defined in the Object Database Management Group (ODMG) 2.0 specification. I recomm
end downloading PSE and working with it for a few weeks (it's good for files with up to tens of megabytes of data). Then, if it looks right for your application, you can graduate to PSE Pro or ObjectStore.
Where to Find
Object Design, Inc.
Burlington, MA
Phone: 781-674-5000
Internet:
http://www.odi.com