ObjectDB ObjectDB

[ODB1] Chapter 6 - Persistent Objects

6.1  Making Objects Persistent

In JDO applications, every object in memory is either a persistent object, i.e. represents some database content, or a transient object, i.e. not related to any database.

Storing Objects Explicitly

When a new object is constructed by the new operator, it always starts as a transient object, regardless of its type. Instances of persistent classes can become persistent later, as demonstrated by the following code:

  pm.currentTransaction().begin();

  Person person = new Person("George", "Bush");
  Address address = new Address("White House");
  person.setAddress(address);
  pm.makePersistent(person); 

  pm.currentTransaction().commit();

First, the Person instance is constructed as a simple transient object. Then it becomes persistent by an explicit call to the makePersistent(...) method. When a transient object becomes persistent during an active transaction, its content is automatically stored in the database when the transaction is committed (unless it is being deleted before commit). An attempt to call makePersistent(...) when there is no active transaction throws an exception, because operations that modify the database require an active transaction. An exception is also thrown if the argument to makePersistent(...) is not an instance of a persistent class (as defined in chapter 3).

Only the call to makePersistent(...) has to be within an active transaction. Therefore, the following code is legal and equivalent to the code above:

  Person person = new Person("George", "Bush");
  Address address = new Address("The White House");
  person.setAddress(address);

  pm.currentTransaction().begin();
  pm.makePersistent(person); 
  pm.currentTransaction().commit();

Multiple objects can become persistent, either by multiple calls to makePersistent(...), or by passing a collection or array of objects to makePersistentAll(...), which is another method of PersistenceManager. The makePersistentAll(...) method stores all the elements in the specified collection or array in the database, but not the collection or array itself (unlike passing a collection to Utilities.bind(...)).

Persistence By Reachability

The Address instance in the code above also starts out as a transient object. It, however, becomes persistent, not by an explicit call to makePersistent(...), but rather, because of the persistence by reachability rule. According to this JDO rule, every transient object that, at commit time, is reachable from a persistent object (using persistent fields) also becomes persistent, i.e. its content is also stored in the database. This mechanism prevents broken references in the database and simplifies development.

The JDO specification limits the use of makePersistent(...) to instances of user defined persistent classes. However, persistence by reachability is applicable to all persistent types, including system types, such as String and ArrayList (a complete list of persistent types is available at section 3.2). ObjectDB provides an additional storing method, Utilities.bind(...). This method (discussed in the next section) enables the assignment of names to objects in the database and the storing of instances of any persistent type explicitly, including system types.

Embedded Objects

An embedded object is an object that is stored in the database as part of a containing object. Objects stored explicitly by makePersistent(...) are never embedded. Objects stored as a result of persistence by reachability can be embedded. By default, system types are embedded and user defined types (like the Address instance above) are not. This behavior can be changed as explained in section 4.3

Embedded objects can reduce storage space and improve efficiency, but they also have some limitations. An embedded object cannot have an object ID. An embedded object cannot be shared by references from multiple objects. In addition, embedded objects of persistent classes are not included in the extents of their classes, so they cannot be iterated or queried directly (an embedded object can only be retrieved using a reference from its containing object).

6.2  Object IDs and Names

Identifying database objects by unique IDs and by names is useful in object retrieval, as shown in the next section (section 6.3). ObjectDB supports two identifying methods for database objects. The first method, object IDs, is supported by JDO. The second method, object names, is not supported by JDO, but ObjectDB supports it as an extension because of its popularity in object databases. Both methods are only supported for non embedded objects.

Object IDs

ObjectDB assigns a unique numeric ID to every persistent object when it is first stored in the database. The ID of the first object in the database is always 1, the ID of the second object is 2, and so on. Once assigned, the object ID cannot be modified or replaced. It represents the object as long as the object exists in the database. If an object is deleted from the database, its unique ID is never used again to represent another object. The JDO specification refers to object IDs assigned automatically by the JDO implementation as datastore identity. Application identity, in which the application assigns object IDs to objects, is not supported by ObjectDB.

Accessing the object ID of a persistent object can be done as follows:

  Object oid = pm.getObjectId(obj);

Or by a static method (useful when a PersistenceManager is unavailable):

  Object oid = JDOHelper.getObjectId(obj);

JDO does not define a specific type for object IDs, and different JDO implementations may use different types. Therefore, a JDO portable application should manage Object IDs using java.lang.Object references. You can assume that whatever type is used by a given JDO implementation, it implements the Serializable interface and it has a meaningful toString() method. The toString() method of object IDs in ObjectDB returns the numeric ID of the object as a string. The ability to convert Object IDs to strings, and the ability to convert strings back to object IDs (as shown in the next section), make object IDs very useful for representing objects in web applications (in which parameters are represented by strings).

If getObjectID(...) is called with a transient parameter, it returns null. If it is called with a new persistent object that has not been stored in the database yet, an incomplete object ID is returned, and its toString() returns a temporary negative number, because a database object ID is not allocated yet. Such an object ID is fixed on commit(). For example:

pm.currentTransaction().begin();
Person person = new Person("George", "Bush");
pm.makePersistent(person);
Object oid = pm.getObjectId(person);
String s1 = oid.toString(); // s1 contains a temporary negative number 
pm.currentTransaction().commit();
String s2 = oid.toString(); // s2 contains a permanent positive number

The above code shows that calling toString() on the same object ID before and after commit() returns different values. Using temporary IDs instead of allocating permanent object IDs immediately on makePersistent(...) improves efficiency because the database only has to be accessed once, as part of the commit().

Object Names

ObjectDB supports assigning names to specific persistent objects, as shown in the following code:

import com.objectdb.Utilities;
    : 
    : 
  pm.currentTransaction().begin();
  Utilities.bind(pm, person, "me");
  pm.currentTransaction().commit();

The Utilities.bind(...) method is very similar to the makePersistent(...) method, and can only be called within an active transaction. When the transaction is committed, the object is stored in the database with the specified name. If the object is already in the database, the new name is assigned to it but its content is only updated if it has changed since the last store. An object name must be unique in the database. The bind(...) method throws a JDOUserException if the name is already in use.

In addition to the naming issue, there is another difference between makePersistent(...) and bind(...). The bind(...) method is not restricted to only storing instances of persistent classes, but can directly store instances of any persistent type in the database. A complete list of persistent types is shown in section 3.2.

The Utilities.unbind(...) method removes an object name:

import com.objectdb.Utilities;
    : 
    : 
  pm.currentTransaction().begin();
  Utilities.unbind(pm, "me");
  pm.currentTransaction().commit();

Removing a name also requires an active transaction. When the transaction is committed, the name is removed from the database. Notice that unbind(...) is not exactly an inverse operation of bind(...), because the object is not deleted from the database, just its name is removed.

Objects with names are called root objects in object databases, because navigation in the database can easily start from them, as explained in the next section.

6.3  Retrieving Persistent Objects

There are various ways to retrieve objects from an ObjectDB database. Every object can be retrieved directly by using its object ID. A root object can also be retrieved by its name. It is also possible to use an Extent instance to iterate over all the objects of a specific class, retrieving them from the database one by one. If more selective retrieval is needed, a Query can be used to retrieve only objects that satisfy some well defined criteria. In addition, after an object is retrieved, all the objects that are reachable from it can be retrieved simply by navigating through the persistent fields (this is part of the transparent persistence feature of JDO). No matter which way an object is retrieved from the database, its no-arg constructor is used to construct a memory object to represent it. Therefore, for efficiency sake, it is recommended to avoid time consuming operations in no-arg constructors of persistent classes, and to keep them as simple as possible.

The Persistent Object Cache

As noted before, persistent objects are memory objects that represent some content in the database. When an object is retrieved from the database, no matter which method is used, ObjectDB has to return a persistent object representing that database object. Every PersistenceManager instance manages a cache of persistent objects that are in use by the application. If the cache already contains a persistent object representing the requested database object, that object is returned. If an object is not found in the cache, a new object is constructed, filled with the content of the database object, added to the cache and finally returned to the application. Persistent objects that have not been modified or deleted during a transaction are held in the cache by weak references. Therefore, when a persistent object is no longer in use by the application the garbage collector can discard it, and it is automatically removed from the cache.

The main role of the persistent object cache is to make sure that, within the same PersistenceManager, a database object is represented by no more than one memory object. Every PersistenceManager instance manages its own cache and its own persistent objects. Therefore, a database object can be represented by different memory objects in different PersistenceManager instances. But retrieving the same database object more than once using the same PersistenceManager instance should always return the same memory object. In addition to the object cache that is required by JDO, ObjectDB manages another cache containing database pages, in order to improve efficiency. Unlike the object cache, which is always on the client side, the page cache is located on the server side in client-server mode.

Retrieval by an Object ID

If an object ID instance, oid, is available, an object can be retrieved simply by:

  Person person = (Person)pm.getObjectById(oid, false);

The second argument of getObjectById(...) indicates whether or not the retrieved object has to be verified against the database, in case it is already found in the cache and it is unclear if its content in memory is up to date. If true is specified and the object in database is different, the cached object is refreshed from the database, and only then it is returned.

A more common scenario is starting with a string representation of an Object ID (obtained once by calling toString() on a real object ID instance), rather than an actual object ID instance. This is a classic case in web applications, in which passing string values between web pages is easier than passing real object ID instances. In this case an additional step is needed to build the object ID instance from a string str that represents it:

  Object oid = pm.newObjectIdInstance(Person.class, str);
  Person person = (Person)pm.getObjectById(oid, false);

Notice that the newObjectIdInstance(...) method requires not only an object ID string, but also a Class instance. ObjectDB ignores the first argument. Therefore, a null value can also be specified, but in order to keep the application JDO portable, a proper Class should be specified. The class can also be encoded in an object ID string, using:

  String str = JDOHelper.getObjectId(obj) + '@' + obj.getClass();

In this case the object can be retrieved later using str by:

  String[] s = str.split("@");
  Object oid = pm.newObjectIdInstance(Class.forName(s[0]), s[1]);
  Person person = (Person)pm.getObjectById(oid, false);

Retrieval by an Object Name

A root object, which is a database object with an assigned name, can be retrieved by using its name. For example, a root with the name "me" can be retrieved with:

  Person person = (Person)pm.getObjectById("me", false);

This is the same getObjectById(...) method that is used to retrieve objects by their object IDs, but when the first argument is a String instance, it is considered an object name. As already noted, object names are supported by ObjectDB but not by JDO.

Retrieval by an Extent

An Extent enables iteration over all the persistent instances of a specific persistent class (excluding embedded objects). The objects are retrieved one by one:

  Extent extent = pm.getExtent(Person.class, false);
  java.util.Iterator itr = extent.iterator();
  try {
    while (itr.hasNext())
    {
      Person person = (Person)itr.next();
      System.out.println(person.getName());
    }
  }
  finally {
    extent.close(itr); // closes a specified iterator
    //extent.closeAll(); // closes all active iterators
  } 

An Extent instance is obtained by the PersistenceManager's getExtent(...) method. The persistent class for which the Extent is required is specified by the first argument. The second argument specifies whether or not persistent instances of subclasses should also be included (true indicates that subclasses are required).

Iteration over an Extent is similar to iteration over any ordinary Java collection using an Iterator instance. Each call to next() retrieves another instance of the class from the database, until hasNext() returns false, which indicates there are no more instances. In ObjectDB (but not necessarily in other JDO implementations) the iteration order is from the oldest object to a newest, i.e. from a lower object ID to a higher one.

Unlike an iterator over an ordinary Java collection, an iterator over an Extent should be closed at the end of its use. This is mainly important in client server mode in which an active iterator might consume resources in the remote server. The Extent's close(...) method closes a specified iterator. Alternatively, all the active iterators on an Extent can be closed by the Extent's closeAll() method (the commented line in the code above).

Retrieval by Query

The most advanced method for retrieving objects from the database is to use queries. The official query language of JDO is JDOQL (JDO Query Language). It enables retrieval of objects from the database using simple queries as well using complex, sophisticated queries. The results can also be sorted by specified order expressions. Chapter 7 is devoted to JDOQL queries.

Retrieval by Access

When an object is retrieved from the database (in whatever manner) all its persistent fields can be accessed freely. This includes fields that reference other persistent objects, which may or may not have not been loaded from the database yet (and may be seen as null values in the debugger). When an access to another persistent object is made, the object is retrieved automatically by ObjectDB from the database if it is not already present. From the point of view of the developer, it looks like the entire graph of objects is present in memory for his or her use. This illusion is part of the transparent persistence feature of JDO, which hides some of the direct interaction with the database, in order to make database programming easier.

For example, after retrieving a Person instance from the database, its address field can be accessed, causing an Address instance to be loaded from the database automatically:

  Person person = (Person)pm.getObjectById(oid, false);
  Address address = person.address; // an Address instance is retrieved 

To enable this automatic retrieval, special JDO code has to be added to every class in which persistent fields are accessed or modified directly. The addition of this code to persistent classes, along with the addition of the implementation code for the PersistenceCapable interface, is done automatically by the JDO enhancer. Non persistent classes that directly access persistent fields of other classes are referred to as persistence aware. Persistence aware classes must also be run through the JDO enhancer, which enhances them to include the code needed for transparent persistence. But, because these classes are not described in the JDO metadata files, the enhancer identifies them as not persistent and does not enhance them with the code that implements the PersistenceCapable interface. Persistence aware classes are discussed in more detail in section 3.3.

Refresh and Retrieve

The PersistenceManager interface includes some methods for special retrieval operations. The retrieve(...) method is used to load all the fields of a specified persistent object that have not been loaded yet, without waiting for them to be accessed. A collection or array of persistent objects can be passed to retrieveAll(...), for the same purpose. The refresh(...) method is used to reload an object from the database. If the object was modified since it was previously loaded, and the changes were not committed yet, they are discarded. Multiple objects can be refreshed, either by multiple calls to refresh(...), or by a single call to refreshAll(...).

Non Transactional Persistent Objects

Because NonTransactionalRead is enabled in ObjectDB by default, objects can also be retrieved without an active transaction. Persistent objects outside an active transaction are referred to as non transactional persistent objects. By default, these objects are read-only and a JDOUserException is thrown on any attempt to modify them (unless NonTransactionalWrite is enabled as explained in section 5.3).

6.4  Modifying Persistent Objects

Modifying existing database objects using JDO is straightforward, because it is also based on transparent persistence. After retrieving a persistent object from the database (no matter which way), it can simply be modified in memory while a transaction is active. When the transaction is committed, the content of that object in the database is physically updated:

  pm.currentTransaction().begin();
  Person person = (Person)pm.getObjectById(oid, false);
  person.address = new Address("Texas"); // persistent field is modified
  pm.currentTransaction().commit();

If, during a transaction, a transient object becomes reachable from a persistent object (as the new Address instance above), it is also stored in the database as a new persistent object, following the persistence by reachability rule. Of course, if the transaction is ended with rollback() and not with commit(), all the modifications are discarded, and nothing is propagated to the database. It is important to note in the code above that an old Address instance, if there were one, would not be deleted automatically. Section 6.5 discusses persistent object deletion.

When NonTransactionalRead is enabled (the default in ObjectDB), only the modification of the object is required to occur within an active transaction. The object itself can be retrieved before the transaction begins: Person person = (Person)pm.getObjectById(oid, false); pm.currentTransaction().begin(); person.address = new Address("Texas"); pm.currentTransaction().commit();

However, if possible, it is better to retrieve an object for modification in the same transaction in which it is modified because this is slightly more efficient.

Notice that any code that modifies persistent fields must be enhanced, either as persistence capable or as persistence aware. Otherwise, modifications cannot be tracked by ObjectDB, and at transaction commit, cannot be propagated to the database.

6.5  Deleting Persistent Objects

In order to delete an object from the database, it must first be retrieved (no matter which way), and then, when a transaction is active, it can be deleted using the deletePersistent(...) method:

  pm.currentTransaction().begin();
  Person person = (Person)pm.getObjectById(oid, false);
  pm.deletePersistent(person); // object is marked for deletion 
  pm.currentTransaction().commit(); // object is physically deleted

The object is physically deleted from the database when the transaction is committed. If the transaction is rolled back and not committed, the object is not deleted. Only the call to deletePersistent(...) must be made within an active transaction. The object can be retrieved before the transaction begins (even though it is slightly less efficient).

Multiple objects can be deleted, either by multiple calls to deletePersistent(...), or by passing a collection or an array of objects to deletePersistentAll(...), which is also a method of PersistenceManager.

Deleting Dependent Objects

When an object is deleted from the database, all the embedded objects that it contains are also deleted automatically. Non embedded objects referred to by the deleted object are not deleted with it, so if they are not needed anymore they have to be deleted explicitly. An elegant way to delete non embedded dependent objects is to implement the InstanceCallback interface, as demonstrated in section 3.4.

Usually, instances of system persistent types are embedded by default, while instances of user defined persistent classes are not (more details are provided in section 4.3 and section 4.4). Therefore, by default, String and Date fields, as well as collection fields (such as ArrayList) are deleted automatically when their containing object is deleted. When possible, it is useful to define dependent objects of user defined classes as embedded. In addition to being simpler to delete, embedded objects are usually more efficient in terms of execution time and database space. However, because of the limitations of embedded objects (as discussed in section 6.1), using them is not always an option.

Database Garbage Collector

The database garbage collector is a tool that scans an ObjectDB database, locates unreachable objects, and deletes them from the database. Instances of a persistent class with Extent support (excluding embedded objects) are always considered reachable. Root objects (objects with names) are also always reachable. In addition, every object that can be accessed by navigation from a reachable object using persistent fields is also reachable. On every run of the garbage collector, all the unreachable objects are located and deleted.

Unlike the Java memory garbage collector, execution of the database garbage collector requires an explicit call. It can be run in the Database Explorer, as discussed in chapter 9, or by the application:

import com.objectdb.Utilities;
    : 
    : 
  Thread thread = Utilities.startGarbageCollector(pm);

Only a single database garbage collector thread can run at any given time. But, the database garbage collector does not require exclusive access to the database and can run while the database is also being used by other processes or threads. The Thread instance returned from Utilities.startGarbageCollector(...) may be used to manipulate the garbage collector thread (i.e. to change priority, to wait until it finishes, and so on).

Even if you do not use the database garbage collector for production, it may be useful for debugging and testing. If the garbage collector locates unreachable database objects, the application can be fixed to delete these objects before they become unreachable. JDO portable applications should not use the garbage collector as it is an ObjectDB extension and probably not supported in other JDO implementations.

Note: The database garbage collector is not supported by the ObjectDB free edition.

6.6  Object States

Objects in a JDO application are divided into persistent objects, which represent database objects, and transient objects, which are ordinary memory objects not related to any database. States of persistent objects are distinguished by several factors. This section contains a brief description of object states in JDO. More details can be found in any book on JDO.

Examining Object States

The state of an object in a JDO application can be examined using five JDOHelper static methods:

  boolean b1 = JDOHelper.isPersistent(obj);
  boolean b2 = JDOHelper.isTransactional(obj);
  boolean b3 = JDOHelper.isNew(obj);
  boolean b4 = JDOHelper.isDeleted(obj);
  boolean b5 = JDOHelper.isDirty(obj);

All of these methods return false if obj is not persistent (i.e. a transient object or null).

If obj is persistent, i.e. isPersistent(obj) returns true, the other methods provide more details:

JDOHelper.isTransactional(obj)

Returns true if obj is transactional, i.e. if obj is associated with an active transaction. Every object that becomes persistent or retrieved from the database during an active transaction is transactional. Objects retrieved from the database when no transaction is active are non transactional. Transactional objects become non transactional automatically when the transaction is ended. Non transactional objects become transactional if they are accessed or modified when a transaction is active.

JDOHelper.isNew(obj)

Returns true if obj is a new persistent object of the current transaction, and not yet stored in the database.

JDOHelper.isDeleted(obj)

Returns true if obj has been physically deleted from the database or if obj has been marked for deletion in the current transaction using deletePersistent(...) and it is expected to be deleted at transaction commit.

JDOHelper.isDirty(obj)

Returns true if the state or the content of obj has been changed in the current transaction. Particularly, it returns true if isNew(obj) or isDeleted(obj) return true, and also if one of the persistent fields of obj has been modified.

Object States and Locks

As explained in section 5.3, ObjectDB manages an automatic lock mechanism in order to prevent two or more different PersistenceManager instances from modifying the same object at the same time. When using data store transactions (which is the default in ObjectDB), there is a strong relationship between object states and object locks.

Only persistent transactional objects that are not new are locked. Therefore, a persistent object obj is locked only when the following expression evaluates to true:

  JDOHelper.isPersistent(obj) &&
  JDOHelper.isTransactional(obj) &&
  !JDOHelper.isNew(obj)

When a JDOHelper.isDirty(obj) is true a WRITE lock is used and when it is false a READ lock is used. When the state of the object is changed (for example a persistent object is modified) an automatic action is taken by ObjectDB to update the object lock (for example from READ to WRITE). Locks are released automatically when the transaction is ended because, at that point, all the objects have become non transactional. If these objects are later accessed in a new transaction, they are then locked again because they become transactional.

Changing Object States

An object's state can change either implicitly or explicitly. Implicit changes occur when its fields are accessed or modified, or when the transaction associated with it is ended. An object’s state is changed explicitly when the object is passed as an argument to a method like makePersistent(...) or deletePersistent(...). The PersistenceManager interface includes other methods for changing object states, which are not discussed in this guide, because in most applications they are rarely used.

Among them:

  • evict(...) and evictAll(...),
  • makeTransactional(...) and makeTransactionalAll(...),
  • makeNonTransactional(...) and makeNonTransactionalAll(...),
  • makeTransient(...) and makeTransientAll(...)

The last two methods, for instance, can change the state of one or more persistent objects to transient. A persistent object that becomes transient is not managed by JDO anymore, and it is immediately removed form the persistent object cache. More details about these methods can be found books on JDO.