Guide  Reference  Contents  Previous  Next  Index



Defining Persistence-Capable Classes

A persistence-capable class is one whose instances can be made persistent and saved in a database. When you define a persistence-capable class, you must consider its position in the inheritance hierarchies of the application, the range of persistent behavior that the class should support publicly and privately, and which of its fields contain persistent data. This chapter discusses the decisions you must make when defining a persistence-capable class and describes how to implement those decisions.

In This Chapter

Persistence-Capable Classes
Persistors
Persistence Behavior
Making a Class Persistence-Capable
Inheriting From ooObj
Getting and Setting an Object's Persistor
Handling Persistent Events
Providing Explicit Persistence Behavior
Delegating Persistent Operations
Adding Persistence Capability to Third-Party Classes
Defining Fields
Persistent Fields
Transient Fields
Linking Objects Together
Defining Access Methods
Field Access Methods
Relationship Access Methods
Defining Application-Required Methods

Persistence-Capable Classes

A persistence-capable class supports persistent operations, allowing instances of the class to act both as normal Java runtime objects and as objects stored persistently in a federated database. An application that needs to save objects in a database must define a persistence-capable class for each kind of object to be saved.


Note: You should not use the underscore character (_) in the name of a persistence-capable class. If you are adding persistence to an existing class whose name contains underscore characters, you must give the class a schema class name that does not contain underscores. See "Schema Class Names".

Applications may also work with Objectivity for Java persistence-capable classes, namely classes for containers and for collections of persistent objects. An application that needs to associate persistent data with a container can define its own application-defined container classes.

Descriptions of all persistence-capable classes are stored in the schema of a federated database. Several chapters in this guide discuss various aspects of working with a schema:

Persistors

Every persistent object has an associated internal object called a persistor, an instance of a class that implements the PooObj interface. An object's persistor contains all the internal database states for the object and implements persistent behavior for the object.

Persistence Behavior

Persistence-capable classes can support three general kinds of persistence behavior:

Explicit Persistence Behavior

A persistor has methods that:

A persistence-capable class can implement corresponding methods to provide some or all of this persistence behavior explicitly.

Persistent Events

A persistent event is a pre- or post-processing event; when a persistent object is involved in certain persistent operations, the object receives a persistent-event notification immediately before or after the persistent operation occurs. In response to the notification, the object can perform whatever application-specific processing is required.

Persistent events occur entirely within the Objectivity for Java process space (they are not generated asynchronously by other processes and dispatched to Objectivity for Java). These events are also session specific; that is, a persistent operation in one session affects only the persistent objects that belong to that session.

Objectivity for Java supports three kinds of persistent events:

Making a Class Persistence-Capable

You can make an application-defined class persistence-capable in any of four ways:

The class inherits default implementations for public methods that get and set an object's persistor, that perform persistent operations explicitly, and that handle persistent events. You do not need to implement any persistent behavior unless you want to modify the default implementation.
This interface provides public methods to get and set an object's persistor, to perform persistent operations explicitly, and to handle persistent events; you must implement all these methods.
The IooObj interface defines the persistent operations that are available to persistence-capable classes. As such, it may change from release to release. If you define classes that implement IooObj, future releases of Objectivity for Java might require you to make code changes. For example, if a new method is added to the interface, you would need to implement that method for your classes.
This interface has public methods to get and set the persistor and to handle persistent events; you need to implement those methods. If you desire, you can also implement public or private methods to perform persistent operations explicitly.
This interface has public methods to get and set the persistor; you need to implement those methods. If you desire, you can also implement public or private methods to perform persistent operations explicitly.

Once you have defined a persistence-capable class, any subclass you define from it inherits its persistence behavior.

You can also modify third-party classes to make them persistence-capable, but doing so requires care. See "Adding Persistence Capability to Third-Party Classes".

The simplest way to provide the capability for persistence is to define a class that inherits from ooObj. One drawback is that all persistence-capable classes implemented this way form a single inheritance hierarchy with ooObj at the root. If your application already contains disjoint inheritance hierarchies for the classes that you want to make persistence-capable, you can preserve the hierarchies and define the classes to implement one of the persistence-capable interfaces. The choice of which interface determines whether the class can handle persistent events and whether all persistent operations are publicly accessible.

The following table lists the four ways to make a class persistence capable and shows which capabilities are available with each.

Persistence Behavior of ClassClass Definition
Inherits from ooObjImplements IooObjImplements PersistentEventsImplements Persistent
Instances can be made persistentYesYesYesYes
Can get and set object's persistorYesYesYesYes
Can handle persistent eventsYesYesYesNo
All persistent operations are publicYesYesNoNo
Enforces single inheritance hierarchyYesNoNoNo
Includes default implementationYesNoNoNo

Inheriting From ooObj

You can make a class persistence-capable by subclassing ooObj directly, or subclassing some other application-defined class that is derived from ooObj. If you want to associate application-specific data with a container, you can subclass either ooContObj or ooGCContObj.

Example

In this example, Vehicle is a persistence-capable class whose superclass is ooObj; Truck is a persistence-capable class whose superclass is Vehicle.

    import com.objy.db.app.ooObj;
    
    // Make class persistence-capable by inheritance
    public class Vehicle extends ooObj {
        ...

    // Make class persistence-capable by inheritance
    public class Truck extends Vehicle {
        ...
    }

Default Handling for Persistent Events

The ooObj class provides the following default handling for persistent events.

If you want your class to respond differently to any of these persistent events, you must implement the appropriate behavior as described in "Handling Persistent Events".

Getting and Setting an Object's Persistor

Unless your persistence-capable class is a descendant of ooObj, you must implement methods to get and set an object's persistor.

Caching the Persistor

First, decide where to cache each object's persistor. The simplest and most efficient approach is to store the persistor in a field of type PooObj. (The ooObj class uses this approach.) The field holding the persistor must be transient so that it is not stored as part of the data of a persistent object; see "Transient Fields". If the persistor field is not transient, a schema exception will be thrown the first time the class is registered, an object of the class is stored, or an index is defined for objects of the class.

Alternatively, you could cache the persistor in some global (non-persistent) object, such as an array, vector, or hash table.

Initializing the Persistor

When a persistence-capable class is instantiated, its persistor must be initialized to null. A null persistor indicates that the newly created object is transient. When the object becomes persistent, Objectivity for Java gives it a newly created persistor. If the object becomes dead, Objectivity for Java sets its persistor to a dead persistor; see "Dead Persistent Objects".


Warning: Only Objectivity for Java should set an object's persistor. If your application sets an object's persistor, unpredictable behavior and database corruption may result.

Implementing Methods to Get and Set the Persistor

You need to define the two methods declared in the Persistent interface:

The implementations of both methods must be synchronized for thread safety. You can refer to the Objectivity for Java class ooObj for an example implementation of these methods.

Example

This Vehicle class is made persistent by implementing Persistent; its methods to get and set an object's persistor follow the model set forth by ooObj.

    import com.objy.db.iapp.PooObj;
    import com.objy.db.iapp.Persistent;
    
    public class Vehicle implements Persistent {
        // Get and set the persistor
        private transient PooObj persistor;
    
        public synchronized PooObj getPersistor() {
            return persistor;
        }
    
        public synchronized void setPersistor(PooObj persistor) {
            this.persistor = persistor;
        }
        ...
    }

Handling Persistent Events

You need to implement handling for persistent events in any of the following circumstances:

Handler Methods for Persistent Events

A persistence-capable class that can respond to persistent events has a handler method corresponding to each kind of persistent event. A persistent object is notified that a persistent event has occurred by a call to the appropriate handler method. The handler method performs whatever application-specific processing is required to respond to the event.

Activate Events

A persistent object's activate method handles activate events. This method is called after the object is fetched. An activate event is triggered after execution of the fetch or markModified method of the object's persistor if the object's data had not already been fetched.

You might use the activate method to set appropriate values for transient fields or to handle deleted references intelligently.

Deactivate Events

A persistent object's deactivate method handles deactivate events. This method is called for all objects belonging to a session after the session's current transaction is successfully committed or aborted. If an object is made persistent during a transaction that is subsequently aborted, it is still sent a deactivate event.

You might use the deactivate method to allow the application to take different actions depending on whether a transaction is committed or aborted. Doing so can be useful for a user interface or in a work-flow application where an aborted transaction affects the actions of the application.


Note: If you implement the deactivate method in a persistence-capable class, your implementation must not perform any Objectivity/DB operations (because it is called after the transaction has been terminated).

Pre-Write Events

A persistent object's preWrite method handles pre-write events. This method is called before the object is written to the database. A pre-write event can be triggered by any method that causes the object to be written, namely:

You might use the preWrite method to transform or encrypt the values of some persistent fields. Alternatively, your method could check that values in the various persistent fields are mutually consistent; if it finds a problem, it could throw a runtime exception to prevent Objectivity for Java from writing out this particular object. In the latter case, the exception would abruptly terminate the encompassing operation.

Implementing Persistent-Event Handler Methods

Your handler methods may perform whatever application-specific processing is required in response to a persistent event.

The parameter to a handler method is a read-only information object of a class that implements the PersistentEventInfo interface. The information object contains information specific to the persistent event that occurred, for example, why the event was triggered.


Note: If the same persistent operation occurs more than once, whether on the same persistent object or different objects, the information object passed in one call to the handler method may not be identical (equal) to the information object passed in the next call to the same method.

You should follow these guidelines when you implement your handler methods:

Exceptions in Handler Methods

A number of rules govern how Objectivity for Java handles uncaught exceptions thrown by persistent-event handler methods. When exceptions are thrown, it is important to define the state of the objects, whether they are marked as modified or as requiring to have their data fetched.

Exceptions While Handling an Activate Event

Any exception thrown by an object's activate method can affect the state of that object. The following table lists methods that can trigger an activate event and shows the resulting object states and the exception propagation when the activate method throws an exception.

Method Triggering the Activate EventState of Notified Object After the Exception
Default activate MethodApplication-Defined activate Method
fetch Object is marked as not needing its data fetched.Object is marked as needing its data fetched.The exception is propagated.
markModified Object is marked as needing its data fetched.Object is marked as modified.The exception is propagated.Object is marked as not needing its data fetched.Object is marked as not modified.The exception is propagated.

Exceptions While Handling a Deactivate Event

Since more than one object may be notified of this event at any one time, it is not reasonable for Objectivity for Java to stop whenever some object's deactivate method throws an exception. Instead, Objectivity for Java silently consumes such exceptions and continues to notify the remaining objects.

A deactivate event is a courtesy notification in that any exceptions thrown by notified objects' deactivate methods do not affect the commit or abort operation that triggered the event. The database operation completes and the objects are updated according to their fetched and modified state.

If an aborted transaction triggered the event:

An object deleted during a transaction is removed from the session, so its deactivate method is not called when the transaction is committed or aborted. If the transaction is aborted, the deleted objects still exist in the database. However, its corresponding Java object has been marked dead; the object needs to be retrieved again before the application can access it.

Exceptions While Handling a Pre-write Event

The preWrite method is called for each object that is written by the persistent operation that triggered the pre-write event. If some object's preWrite method throws an exception, the operation terminates abruptly; the state of the object throwing the exception is not changed (that is, it is still marked as modified) and the exception is propagated. If the operation involves other objects, the state of each object depends on whether it is written to the cache prior to the exception or after the exception.

The following table illustrates the object states if an exception is thrown.

Method Triggering the Pre-Write EventState of Notified Objects After the Exception
write The object is still marked as modified.
copy The object is not copied; no new instance is returned.
flush Because the processing order during a flush operation is not specified, the remaining objects are not written to the cache and the exception causes abrupt termination of the flush operation. The object that throws the exception is still marked as modified.
commit checkpoint No object is written to the database. Objects written to the cache prior to the exception are marked as not modified. The operation terminates. The object that throws the exception is still marked as modified.
Predicate scanSame as above. No iterator instance is created or returned.

Providing Explicit Persistence Behavior

The methods in the IooObj interface explicitly provide the full range of persistence behavior that is available implicitly through an object's persistor. If your class implements IooObj, you must implement all these methods. If your class instead implements Persistent or PersistentEvents, you may implement any of these methods that you choose.

Your implementation of an explicit persistence method should take the appropriate action if the object is transient or if it is a dead object.

Example

The Vehicle class implements the IooObj interface. Its markModified and fetch methods forward the call, when appropriate, to the object's persistor.

    import com.objy.db.iapp.IooObj;
    import com.objy.db.iapp.PooObj;
    
    public class Vehicle implements IooObj {
        private transient PooObj persistor = null;
        ...    
        // Explicit persistence behavior
        void fetch() {
            if (persistor.isDead())
                throw new ObjectIsDeadException(
            "Attempted persistent operation on dead object");
            // Do nothing if object is transient
            if (persistor != null) 
                persistor().fetch();
        }
    
        void markModified() {
            if (persistor.isDead())
                throw new ObjectIsDeadException(
        "Attempted persistent operation on dead object");
            // Do nothing if object is transient
            if (persistor != null) 
                persistor().markModified();
        }
        ...
    }

To ensure that your class accesses the persistor appropriately, you can copy the implementation of the various persistence methods from ooObj to your class. Note, however, that ooObj uses internal methods that throw exceptions if the object is transient or dead. If you copy implementations from ooObj, be sure to copy definitions of these internal methods as well.

Example

This Vehicle class implements the IooObj interface. Its persistence methods, copied from ooObj, use the internal methods persistor and notDeadPersistor.

    import com.objy.db.iapp.IooObj;
    import com.objy.db.iapp.PooObj;
    
    public class Vehicle implements IooObj {
        // Get and set the persistor
        ...    
        // Internal methods
        private synchronized PooObj persistor() {
            if (persistor == null)
                throw new ObjectNotPersistentException(
        "Attempted persistent operation on transient object");
            if (persistor.isDead())
                throw new ObjectIsDeadException(
        "Attempted persistent operation on dead object");
            return persistor;
        }
    
        private synchronized PooObj notDeadPersistor() {
            if (persistor.isDead())
                throw new ObjectIsDeadException(
        "Attempted persistent operation on dead object");
            return persistor;
        }
    
        // Explicit persistence behavior
        void fetch() {
            if (persistor != null) 
                notDeadPersistor().fetch();
        }
    
        public void write() {
            persistor().write(); 
        }
        ...
    }

Delegating Persistent Operations

Classes that implement either Persistent or PersistentEvents need not define persistence methods. However, each time an object of such a class needs to perform a persistent operation, it must test that its persistor is valid for the desired operation. You avoid repeating the necessary tests with each call and avoid implementing persistent methods for several classes by defining a "delegator" class whose sole purpose is to provide persistence behavior.

Example

This example shows a few methods of a class Delegator whose role is to implement persistence behavior. Any number of persistence-capable classes that implement Persistent or PersistentEvents could use the Delegator class. The complete class definition appears in the Delegator programming example.

    public class Delegator {
        // Internal methods
        private static synchronized PooObj notDeadPersistor(
                PooObj persistor) {
            if (persistor.isDead())
                throw new ObjectIsDeadException(
        "Attempted persistent operation on dead object");
            return persistor;
        }
        ...
        // Explicit persistence behavior
        public static void markModified(PooObj persistor) {
            if (persistor != null)
                notDeadPersistor(persistor).markModified(); 
        }
        ...
    }

Adding Persistence Capability to Third-Party Classes

It is possible to add persistence capability to any class by implementing one of the persistence-capable interfaces. Before doing so however, you should keep in mind the following caveats:

Defining Fields

Regardless of how you make your class persistence-capable, you will follow the same approach when defining fields of the class. Fields can serve two roles for an object. They can capture the state associated with an object or they can link an object to other objects. Persistent objects can have persistent fields, whose values are saved in the database, and transient fields, whose values are not saved.

Persistent Fields

All non-static and non-final fields you define for a persistence-capable class are persistent by default. The values in the persistent fields of a persistent object constitute that object's persistent data. When the object is written to the federated database, the values in those fields are saved persistently.


Note: Only application-defined fields are considered persistent fields. Any fields defined by Objectivity for Java (for example, inherited fields of ooObj, container classes, and persistent-collection classes) are considered part of the internal representation of the object. We refer to the contents of those fields as properties of the object, not the object's persistent data.

Every persistent field must be of one of the following data types:

Category Types
Java primitive typechar
byte
short
int
long
float
double
boolean
Java string classString
StringBuffer
Note: If you set the value of a String persistent field to be an empty string (""), the field will be stored in the database as null. When the object containing the String field is later read back, the field in the Java object will likewise be set to null.
Java date or time classjava.util.Date
java.sql.Date
java.sql.Time
java.sql.Timestamp
Note: An object of a date/time class in a persistent field is stored in the federated database as an internal persistent object.
Persistence-capable classooObj
An application-defined persistence-capable class
A persistent-collection class
A container class Any interfaceNote: Although the declared type of a persistent field may be any interface, the actual object referenced by the field must be a persistent object.
Java array of any of the preceding typesFor example, long[] or String[]Note: An array in a persistent field is stored in the federated database as an internal persistent object. Each element of a String array is also stored as an internal persistent object.
An Objectivity for Java relationshipSee Chapter 7, "Relationships" for information on relationships.

Example

This example illustrates the persistent fields of a class called Vehicle.

    public class Vehicle extends ooObj {
        // Persistent fields
        protected String license;
        protected String type;
        protected int doors;
        protected int transmission;
        protected boolean available;
        ...
    }

As the preceding table indicates, you cannot create persistent fields that reference the database and federated database where an object is stored, the session to which the object belongs, or the object's identifier. You can, however, obtain this information through methods defined on ooObj (or on the object's persistor).

Example

This code fragment illustrates the methods for retrieving the Objectivity for Java properties of a persistent object that implements explicit persistence behavior. The complete method definition appears in the RentalFields.Vehicle programming example .

    public static void printInfo(Vehicle vehicle) {
        // This method must be called during a transaction
    
        // Get vehicle's container
        ooContObj cont = vehicle.getContainer(); 
        // Get vehicle's database
        ooDBObj db = cont.getDB(); 
        // Get vehicle's federated database
        ooFDObj fd = db.getFD();
        // Get vehicle's session
        Session session = vehicle.getSession();
        // Get vehicle's object identifier
        ooId oid = vehicle.getOid();
        ...
    }

If your Java application will interoperate with applications written in C++ and/or Smalltalk, you must select field types that will map to Objectivity/DB data types that are supported by the other languages. For more information on this topic, see Chapter 19, "Schema Matching for Interoperability".


Note: If a persistent object has an array field, when you fetch the object's persistent data, you fetch the entire array and all its elements from the federated database. When you write the object, you write the entire array and all its elements to the federated database.

Transient Fields

Your class can also have transient fields, whose values are not saved when an object is written to the database. To specify that a field is transient, simply give it the transient modifier when you define your class. Transient fields are not modified when the object is read from the database or copied when a persistent object is copied. You can, however, set the value of a transient field after the object is read from the database.

You must define a field as transient in either of the following circumstances:

Example

This example illustrates a transient field dailyRate of the class Vehicle.

    public class Vehicle extends ooObj {
        ...
        // Transient field
        protected transient int dailyRate;
        ...
    }

Linking Objects Together

Many applications work with object graphs, directed graph data structures that consist of objects linked to other objects. Objectivity/DB provides three mechanisms for linking objects together:

Fields With Object References

As in any Java application, you can use fields containing object references to link objects together. A field whose type is a persistence-capable class can represent a link to the object referenced by that field. A field whose type is a Java array of objects of a persistence-capable class can represent links to the objects in the array.

Example

In this example, the Vehicle class has a persistent field fleet to link a vehicle to its rental fleet. The Fleet class has a persistent field vehicles containing a fixed-sized array of one thousand vehicles; this field serves to link a rental fleet to all the vehicles in the fleet.

    public class Vehicle extends ooObj {
        // Persistent fields
        ...
        protected Fleet fleet;
        ...

    public class Fleet extends ooObj {
        static final int FLEET_SIZE = 1000;
        // Persistent fields
        protected Vehicle[] vehicles = 
            new Vehicle[FLEET_SIZE];
        ...
    }

Membership in Persistent Collections

Objects can also be linked together by their membership in persistent collections. A persistent collection can be saved directly (for example, as a named root), or it can be referenced in a persistent field of a persistent object.

You can use a persistent collection to link one object to a group of objects. Instead of defining a field containing a Java array of persistent objects, you can define a field containing a persistent collection. One advantage of collections is that they are of variable size, whereas a Java array's size is fixed. A collection can grow or shrink as needed.

Example

In this example, the vehicles field of the Fleet class has been replaced by a field of type ooMap. Instead of an array of one thousand elements, the rental fleet's vehicles field now contains a map that associates each vehicle in the fleet with an identifying string, such as its license ID. At any time, the map contains only as many vehicles as are in the fleet, which may be more or less than one thousand.

    public class Fleet extends ooObj {
        // Persistent fields
        protected ooMap vehicles = new ooMap();
        ...
    }

See Chapter 9, "Persistent Collections" for additional details about persistent collections.

Relationships

Objectivity/DB provides a mechanism called relationships as an alternative way to link objects together. Objectivity/DB relationships provide a higher level of functionality than referencing objects directly from persistent fields. You can specify the directionality and cardinality of relationships, whether operations on objects propagate along relationships, and how relationships are handled when you create a new copy or a new version of an object. See Chapter 7, "Relationships," for a complete discussion of Objectivity/DB relationships and how to define and use relationships in Objectivity for Java.

Example

This example substitutes bidirectional relationships for the fleet and vehicles fields in the preceding example. The Vehicle class has a one-to-one relationship fleet that relates a vehicle to its fleet. The Fleet class has a one-to-many relationship vehicles that relates a rental fleet to the vehicles it contains. The two relationships are inverses; that is, if a given vehicle's fleet relationship links it to a given fleet, that fleet's vehicles relationship links the fleet to the vehicle.

    package RentalRelations;
    import com.objy.db.app.*;
    
    ...
    public class Vehicle extends ooObj {
        ...
        // Relationships
        private ToOneRelationship fleet;
        protected static ManyToOne fleet_Relationship() {
        return new ManyToOne(
            "fleet",                   // This relationship
            "RentalRelations.Fleet",   // Related class
            "vehicles",                // Inverse 
            Relationship.INLINE_NONE); // Store non-inline
        }

    package RentalRelations;
    import com.objy.db.app.*;
    
    public class Fleet extends ooObj {
        // Relationships
        private ToManyRelationship vehicles;
        protected static OneToMany vehicle_Relationship() {
        return new OneToMany(
            "vehicles",                // This relationship
            "RentalRelations.Vehicle", // Related class
            "fleet",                   // Inverse
            Relationship.INLINE_NONE); // Store non-inline
        }
    }   

Performance Considerations

An array field containing persistent objects has performance overhead relative to a persistent collection field or a relationship. Because the array is part of the persistent data of the containing object, the entire array and all its elements are read from the federated database when you fetch the containing object's data. Similarly, the entire array and all its elements are written to the federated database when you write the containing object's data--even if you did not modify the array or any of the persistent objects it contains.

In contrast, if you use a persistent collection or a relationship to link objects together, the destination objects are read only if they are accessed and they are written only if they are modified.

Defining Access Methods

When you retrieve a persistent object, you obtain an empty, unlocked object. You need to fetch the object's data before you can safely access the object's persistent fields or relationships; the methods that fetch data also lock the object. You can ensure that objects of your class are used safely by accessing fields and relationships only through access methods that fetch data and obtain locks as necessary.

An additional advantage of using access methods is that they hide the implementation you have chosen, which simplifies the update process if you change your implementation during the prototyping or development phases of your project. For example, suppose you decide to replace a field with a relationship or vice versa. You would need to reimplement only your access methods; the code that calls the access methods would remain unchanged. This kind of implementation change modifies the class description in the schema. As a consequence, if you change the implementation of your class after you have deployed your application, you will need to provide a conversion application to convert objects in the federated database from the old implementation to the new implementation. See Chapter 15, "Schema Evolution and Object Conversion".

If your persistence-capable class is derived from ooObj or implements IooObj, your access methods can call the fetch and markModified methods of the persistent object. If your persistence-capable class instead implements the Persistent or PersistentEvents interface, your access methods must call the fetch and markModified methods of the object's persistor. The easiest approach is to implement fetch and markModified methods for your class as described in "Providing Explicit Persistence Behavior". The following descriptions assume that all your persistence-capable classes have fetch and markModified methods.

Field Access Methods

You should define field access methods for every persistent field of a class and call those methods whenever you get or set the value of a persistent field.


Note: Any access method that calls the fetch or markModified method of a persistent object must be called when that object's session is in a transaction. While the object is transient, fetch or markModified have no effect, so they can be called outside a transaction.

Scalar Fields

If a persistent field has a scalar type, you can define one access method to get the scalar value in the field. If the value in the field can be changed, you can define another access method to set the value in the field.

Example

This example illustrates some field access methods for the Vehicle class. Each persistent field has an access method that gets the value of that field; the getLicense method illustrates the form of these methods. The setFleet access method sets the fleet field to the specified fleet. All other persistent fields are initialized when a vehicle object is created; only the available field can be modified after initialization. The field access methods rentVehicle and returnVehicle set the Boolean field available to false and true, respectively. The complete class definition appears in the RentalFields.Vehicle programming example .

    // Field access methods to get persistent field values
    public String getLicense() {
        fetch();
        return this.license;
    }
    ...    
    // Field access methods to set fields
    public void setFleet(Fleet fleet) {   // Set fleet field
        markModified();
        this.fleet = fleet;
    }
    
    public void rentVehicle() {           // Set available field
        markModified();
        this.available = false;
    }
    
    public void returnVehicle() {        // Set available field
        markModified();
        this.available = true;
    }

If your class that implements the Persistent or PersistentEvents interface uses a delegator class instead of implementing explicit persistence behavior (see "Delegating Persistent Operations"), then your field access methods should call the static fetch and markModified methods of the delegator class.

Example

The following field access method uses a delegator. The complete class definition appears in the PersistentInterface.Vehicle programming example .

    // Field access methods to get persistent field values
    public void getLicense() {
        Delegator.fetch(this.getPersistor());
        return this.license;
    }
    
    public void setFleet(Fleet fleet) {   // Set fleet field
        Delegator.markModified(this.getPersistor());
        this.fleet = fleet;
    }

Array Fields

If a persistent field contains a Java array, you can define access methods to get and set the value at a particular array index. If necessary, you can also define an access method to return the array itself.

Example

This example illustrates access methods for a persistent field containing an array. The getVehicle method gets the value at a particular array index and the setVehicle method sets the value at a particular index. The complete class definition appears in the RentalFields.SimpleFleet programming example .

    public Vehicle getVehicle(int n) {
        fetch();
        return this.vehicles[n];
    }
    
    public void setVehicle(
            int n, 
            Vehicle newMember) {
        markModified();
        this.vehicles[n] = newMember;
    }

An alternative approach to accessing array fields directly is to hide the implementation with field access methods that manage the array.

Example

In this example, a new persistent field, numberOfVehicles, keeps track of the number of vehicles in the fleet. The addVehicle method adds a vehicle to the fleet; the deleteVehicle method deletes a vehicle; the findVehicle method gets the vehicle with the specified license ID. The getAllVehicles method gets an enumeration that finds all vehicles in the fleet; this method uses an inner Enumeration class called VehicleItr. The complete class definition appears in the RentalFields.Fleet programming example .

    public void addVehicle(Vehicle newMember) {
        markModified();
        if (findVehicle(newMember.getLicense())
                == null) {
            this.vehicles[this.numberOfVehicles] =
                newMember;
            this.numberOfVehicles++;
        }
    }
    
    public void deleteVehicle(Vehicle vehicle) {
        markModified();
        int i = 0;
        while ((i < this.numberOfVehicles) && 
                (this.vehicles[i] != vehicle)) {
            i++;
        }
        if (i != this.numberOfVehicles) {
            // Vehicle was found; remove it 
            for (int j = i + 1; 
                    j < this.numberOfVehicles; 
                    j++, i++)
                this.vehicles[i] = this.vehicles[j];
            this.numberOfVehicles--;
        }
    }
    
    public Vehicle findVehicle(String license) {
        fetch();
        if (this.numberOfVehicles == 0)
            return null;
        for (int i = 0; 
                i < this.numberOfVehicles; i++) {
            if (this.vehicles[i].getLicense().equals(license))
                return this.vehicles[i];
        }
        return null;
    }
        
    public Enumeration getAllVehicles() {
        fetch();
        return new VehicleItr(this);
    }

Persistent Collection Fields

If a persistent field contains a persistent collection, your field access methods can get and set the persistent collection. An alternative approach is to hide the implementation you have chosen for the field. Instead of methods that get and set the entire collection, you can define access methods that get and set elements of the collection.

Example

This example illustrates access methods for a persistent field containing a map. The addVehicle method adds a vehicle to the map; the deleteVehicle method deletes a vehicle; the findVehicle method gets the vehicle with the specified license ID; the getAllVehicles method returns an iterator that finds all vehicles in the map. Note that the access methods that modify the map call fetch rather than markModified. The call to fetch retrieved the fleet's persistent data, including the map; the map itself obtains the necessary locks and ensures that the map is written if it is modified. The complete class definition appears in the RentalMap.Fleet programming example.

    public void addVehicle(Vehicle newMember) {
        fetch();
        String key = newMember.getLicense();
        if (this.vehicles.isMember(key)) 
            return;
        this.vehicles.add(newMember, key);
        this.numberOfVehicles++;
    }
    
    public void deleteVehicle(Vehicle vehicle) {
        fetch();
        String key = vehicle.getLicense();
        if (this.vehicles.isMember(key)) {
            this.vehicles.remove(key);
            this.numberOfVehicles--;
        }
    }
    
    public Vehicle findVehicle(String license) {
        fetch();
        if (this.vehicles.isMember(license))
            // Cast retrieved object to class Vehicle
            return (Vehicle)this.vehicles.lookup(license); 
        else 
            return null;
    }
    
    public Iterator getAllVehicles() {
        fetch();
        return this.vehicles.elements();
    }

Relationship Access Methods

Chapter 7, "Relationships," describes how you specify and work with relationships. Briefly, a class has a special field for each relationship; when an object of the class is made persistent, the field is automatically initialized to contain a relationship (of one of the classes ToOneRelationship or ToManyRelationship). You call methods of that relationship to link the object to related objects and to get its related objects.

To ensure that relationships are used correctly, you should define relationship access methods for every relationship of a class and call those methods whenever you need to get related objects or establish relationships with other objects.

To-One Relationships

Example

This example illustrates access methods for a to-one relationship. The setFleet method sets the related fleet; the getFleet method gets the related fleet. The complete class definition appears in the RentalRelations.Vehicle programming example .

    // Relationship access methods
    public void setFleet(Fleet fleet) {
        fetch();
        this.fleet.clear();  // Remove any existing relationship
        this.fleet.form(fleet);
    }
    
    public Fleet getFleet() {
        fetch();
        return (Fleet)this.fleet.get();  // Cast to Fleet
    }

To-Many Relationships

Example

This example illustrates access methods for a to-many relationship. The addVehicle method adds a related vehicle; the deleteVehicle method removes a vehicle from the relationship; the findVehicle method gets the related vehicle with a particular license ID; the getAllVehicles method returns an iterator that finds all related vehicles. The complete class definition appears in the RentalRelations.Fleet programming example .

    // Relationship access methods
    public void addVehicle(Vehicle newMember) {
        fetch();
        if (this.vehicles.includes(newMember))
            return;
        this.vehicles.add(newMember);
        this.numberOfVehicles++;
    }
    
    public void deleteVehicle(Vehicle vehicle) {
        fetch();
        if (this.vehicles.includes(vehicle)) {
            this.vehicles.remove(vehicle);
            this.numberOfVehicles--;
        }
    }
    
    public Vehicle findVehicle(String license) {
        fetch();
        String predicate = new String("license == \"" + 
                                      license + "\""); 
        Iterator itr = this.vehicles.scan(predicate);
        if (itr.hasNext())
            return (Vehicle)itr.next(); // cast to Vehicle
        else
            return null;
    }
    
    public Iterator getAllVehicles() {
        fetch();
        return this.vehicles.scan();
    }

Defining Application-Required Methods

You can define any methods for your persistence-capable classes that your applications require. Note, however, that Objectivity/DB saves only data persistently, not methods. Thus, if more than one application needs to use persistent objects of a given class, each application must have a definition of that class that includes both declarations for its persistent fields and implementations for its application-defined methods.


Note: If you define a finalize method for a persistence-capable class, you must not perform any persistent operation in that method. Different implementations of the Java virtual machine may execute finalize methods in a separate thread. If a finalize method running in its own thread accesses a persistent object whose session enforces the restricted thread policy, a NotJoinedException will be thrown.



Guide  Reference  Contents  Previous  Next  Index



Copyright © 2000, Objectivity, Inc. All rights reserved.