By implementing callback methods , an application can intercept some of the above lifecycle events. There are three major cases in which an application developer might wish to implement some special behavior of persistent classes. These cases are as follows :
The callback methods are placed in an extra interface, which is handled like the java.io.Serializable tag interface. If a persistence-capable class implements the InstanceCallbacks interface, the callback methods are called by the JDO implementation; otherwise , they are not called. 4.4.1 Uses of jdoPostLoadThe jdoPostLoad method is called whenever a persistent object is loaded with data from the datastore. This happens when the instance's state transitions from hollow to persistent-clean . It is precisely called after the object has consistent data in its default fetch group .
By implementing the jdoPostLoad method, the code to check for initial assignment of transient values can be saved, because JDO defines that this method is called only once, when an object is resolved. A class might implement the jdoPostLoad method to assign a value to a transient field, which depends on the value of a persistent field. This is especially useful for extensive calculations that should be done only once. Another use case can be to register the persistent object with other objects of the application after it has been loaded from the datastore. As an example, if deferring the initialization of a collection, memory is conserved and performance is gained until the object is actually used: public class Book implements javax.jdo.InstanceCallbacks { transient Map myMap; // not initialized here! public Book(String title) { // used by application ... } private Book() { // used by JDO runtime ... } public void jdoPostLoad() { myMap = new HashMap(); // initialized when loaded } // other callbacks: public void jdoPreStore() { } public void jdoPreDelete() { } public void jdoPreClear() { } } 4.4.2 Uses of jdoPreStoreThe prestore callback method is called before fields are written to the datastore. One application of this callback is to update persistent fields with values of non-persistent fields before the transaction completes. Another idea can be to check values of persistent fields for consistent values or data constraints. In the event that a constraint has not been followed, an exception can be thrown so that current values cannot be stored. On the other hand, business objects should expose validation methods to check for business constraints. These methods should be invoked by the application before data is written to the datastore. It is not a good practice to couple this kind of validation with the persistency framework. It is better to use the jdoPreStore callback only to test for programming mistakes in this case. Here is an example of how to update a byte array field with a serialized stream of some non-persistent object: public class Book implements InstanceCallbacks { transient Color myColor; // not persistence-capable private byte[] array; // supported by most JDO implementations public void jdoPostLoad() { ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream(array)); myColor = (Color)in.readObject(); in.close(); } public void jdoPreStore() { ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bo); out.writeObject(myColor); out.close(); array = bo.getBytes(); } // other callbacks: public void jdoPreDelete() { } public void jdoPreClear() { } } 4.4.3 Uses of jdoPreDeleteThis callback is invoked before an object is deleted from the datastore ”for instance, when the application calls PersistenceManager.deletePersistent . The jdoPreDelete method is not called for objects that are deleted by other clients of the same datastore, nor for instances in other, parallel transactions. Instead, the jdoPreDelete callback should be used for in-memory instances only. A typical application for the callback is to delete dependent subobjects: public class BookShelf implements InstanceCallbacks { // a BookShelf can contain either Collection shelves; Collection books; public void jdoPreDelete() { // get our PM: PersistenceManager pm = JDOHelper.getPersistenceManager(this); // delete shelves but not the books! pm.deleteAll(shelves); } // other callbacks: public void jdoPostLoad() { } public void jdoPreStore() { } public void jdoPreClear() { } } Long discussions came up in the JDO community about whether the handling of dependent objects should be coded in the Java source or declared somewhere outside in the JDO XML file. In another use case, the jdoPreDelete callback is responsible for de-registering the object in the referencing object, as was mentioned before. The coding might become quite complex if the application's object model gets larger. Here is a small example: public class Aisle implements InstanceCallbacks { // an Aisle contains BookShelves: Collection shelves; } public class BookShelf implements InstanceCallbacks { final Aisle aisle; // location of this shelf // can be created together: public BookShelf(Aisle ai) { this.aisle = ai; aisle.shelves.add(this); } private BookShelf() { /* JDO required */ } // on deletion, remove ourselves from // the enclosing object: public void jdoPreDelete() { aisle.shelves.remove(this); } // other callbacks: public void jdoPostLoad() { } public void jdoPreStore() { } public void jdoPreClear() { } } In JDO, it is always an option to model the relations between objects either as a forward reference (the Aisle has BookShelves ) or as a backward reference (the BookShelf belongs to an Aisle ). A query is necessary to navigate from an enclosing object to its child (get a BookShelf that references a specific Aisle ). It is application-specific and depends on the use cases and which option is taken, but when callbacks are used to de-register an object from its referencing object, a back reference ( Bookshelf.aisle ) is necessary anyway. 4.4.4 Uses of jdoPreClearThe jdoPreClear callback is called when an object becomes hollow or transient from persistent-deleted , persistent-new -deleted, persistent-clean, or persistent-dirty during transaction termination. It should be used to clear non-persistent references to other objects to help the garbage collector, or to de-register it from other objects. It can also be used to clear transient fields, or to set them to illegal values, to prevent accidental access of cleared objects. |