Guide  Reference  Contents  Previous  Next  Index
A standard practice in object modeling is to capture the associations or links between the objects in a system. You can implement such links by first defining fields of a class to reference other objects and then filling in those fields when objects are created. You are responsible for managing the link each time the linked objects are modified.
Objectivity/DB provides a capability for implementing links, called relationships, that provide a higher level of functionality than simply using references to objects. Objectivity/DB maintains relationships in the database. Operations on a group of related objects, known as a composite object, are handled by the database, thus reducing the amount of work you have to do to accomplish such tasks.
Objectivity/DB allows you to specify the directionality and cardinality of relationships, how relationships should be handled when objects are copied or versioned, whether operations on objects propagate along relationships, and how relationships should be stored. This section describes the properties and behavior of Objectivity/DB relationships.
Relationship directionality is defined by the declaration of traversal paths that enable applications to locate related objects. When a traversal path from class A to class B is declared, the relationship is unidirectional. When two traversal paths, one from class A to class B and an inverse path from class B to class A, are declared, the relationship is bidirectional.
An object that maintains a unidirectional relationship can locate its related object, but the related object cannot locate the relating object. Unidirectional relationships correspond most closely to fields that contain object references in a standard Java object model.
Bidirectional relationships allow two related objects to locate each other. These relationships can be connected and disconnected with a single method invocation; adding or removing a relationship in one direction simultaneously adds or removes the inverse relationship. In addition, bidirectional relationships provide Objectivity/DB with enough information to maintain referential integrity; when an object is deleted, all relationships referencing that object are also deleted, reducing the likelihood of dangling object identifiers.
In contrast, it is not possible to ensure that a unidirectional relationship references a valid object. Unidirectional relationships do however, require somewhat less overhead and offer better performance than bidirectional relationships.
If you are modeling a salesperson and the purchasing contacts they maintain, then you must choose whether to model this as a unidirectional relationship giving the salesperson access to the contacts, or as a bidirectional relationship giving salesperson and contact objects access to each other. If it is necessary to be able to find the salesperson responsible for a given contact, then a bidirectional relationship should be used.
A relationship's cardinality indicates the number of objects on one side of a relationship that may be related with objects on the other side. Objectivity/DB relationships support four categories of cardinality:
In the example of the salesperson with many purchasing contacts, the salesperson has a one-to-many relationship with the contacts. For a bidirectional relationship, the contacts have a many-to-one relationship with the salespersons. If the salespersons share contact information, then a many-to-many relationship would be appropriate.
Objectivity/DB allows you to specify how relationships are handled when a copy or new version of an object is created. The possible options are:
The copy behavior for one path of a bidirectional relationship has the same affect on the inverse path of that bidirectional relationship. However, you can specify move for one direction of the relationship paths and copy for the other. When an object of one of the related classes is copied, the relationship path from that object to the related object is handled as requested by the copy behavior specifier for the class.
When a salesperson leaves a company, all their contacts are normally transferred to a different salesperson. One way to implement this would be to make a copy of the original salesperson, letting Objectivity/DB automatically move all the relationships from the old to new salesperson, update the new salesperson's individual data, and then delete the old salesperson.
A given salesperson should, however, be able to make a copy or new version of a contact and maintain a relationship with both the original and new objects. This behavior can be obtained by specifying that when a contact is copied, a relationship with a salesperson should be copied from the original to the new object.
The relationships that result when objects of both classes are copied are illustrated in the following figure. Salesperson specifies copy behavior as move; contacts specify copy behavior as copy. When the salesperson S1 is copied, the salesperson relationship from the contact C1 to S1 is moved, relating C1 with the salesperson S2 and S2 to C1. The bidirectional relationship between C1 and S1 is removed. On the other hand, when contact C1 is copied to contact C2, the contacts relationship from S2 to C1 is copied. The original bidirectional relationship between C1 and S2 is kept and a new bidirectional relationship between C2 and S2 is created.
You can define relationships so that a delete or lock operation will propagate from one object to the next along the relationship. Propagation is a very useful property when you wish to treat related objects as a group. You specify which operations should propagate, and the direction of propagation, when you define the relationships in your classes. Propagation along a relationship is optional, and the default behavior for both delete and lock is non-propagation.
When a propagating operation is applied to an object, Objectivity/DB first identifies all objects that are affected (by identifying relationships that are declared to have propagation). It then applies the operation to all affected objects in a single atomic operation. This guarantees that a propagating operation will eventually terminate, even though the propagation graph may contain cycles. In the example we have been discussing, salesperson and contact objects are fairly loosely coupled. Thus propagation of locking and deleting operations between related objects would be disabled in both directions of the relationship.
For example, suppose your application has a salesperson S1 that maintains a bidirectional association with a contact C1. A bidirectional association means that S1 has a reference R1 to C1 and C1 has a reference ~R1 to S1.
If you enable delete propagation, deleting S1 would cause C1, R1, and ~R1 to be deleted automatically. Because the relationship is bidirectional, referential integrity is automatically enabled. Thus if you deleted C1, R1 would automatically be deleted.
Objectivity/DB relationships can be stored non-inline or inline. The default storage mode is non-inline.
A non-inline relationship is stored in a system default relationship array. Each object with relationships has a system default relationship array in which all non-inline relationships are stored. In the array, each relationship is identified by the relationship name (an identifier, not a string) and the object identifier (OID) of the related object. To trace a particular relationship on an object, Objectivity/DB traverses the relationships in the relationship array until it locates the desired relationship.
You can also define inline relationships. To-one inline relationships are embedded as fields of an object, while to-many inline relationships are placed in their own array instead of the system default relationship array.
There are two types of inline relationships. A long inline relationship uses a long OID to refer to the related object; a short inline relationship uses a short OID to refer to the related object. A short inline relationship uses less storage space to maintain the relationship, resulting in better runtime performance. However, you can use a short inline relationship only for objects in the same container.
The standard storage overhead for a basic object is 14 bytes. This overhead is constant and is independent of an application's use of relationships. The following storage requirements are for unidirectional relationships. Each bidirectional relationship requires storage equivalent to two unidirectional relationships.
A non-inline relationship requires the following additional space:
An inline to-one relationship requires the following additional space:
An inline to-many relationship requires the following additional space:
Since objects are stored on eight-byte boundaries, you should round up your size calculations to the nearest eight bytes.
The following figure illustrates an object with:
Choosing between non-inline and inline relationships depends on how many related objects your application requires. Non-inline relationships use very little space for small numbers of related objects, because the overhead of only one extra array is required. However, there is an implied limit on the number of related objects because the entire array must fit into available swap space when the object is fetched. Also, traversing a non-inline relationship, particularly as the number of related objects gets large, is not very efficient.
Inline relationships have a higher space overhead. To-one inline relationships are embedded within objects, so they take up space even when they are not used. However, traversing inline relationships is very efficient. An object in a one-to-one inline relationship can be retrieved quickly because it is embedded in the object. An object in a one-to-many inline relationship can also be retrieved quickly because the application needs to traverse only that relationship instead of all of the relationships on the object.
You can change how a relationship is stored simply by modifying the class definition containing the relationship. If schema evolution is allowed, the class description in the schema will be evolved to match the Java class definition, and any objects of the class within the database will be converted to the new definition. See Chapter 15, "Schema Evolution and Object Conversion," for more information about what happens when you change a class definition.
Objectivity for Java supports all permutations of conversions between the different ways of storing relationships. You should note, however, the following behaviors that accompany certain types of conversions:
In order to use relationships in Objectivity for Java, you must first define the relationship in the classes whose objects will be related. Relationships may be defined only on application-defined persistence-capable classes; the related class may be any persistence-capable class.
Defining a relationship on a class merely makes it possible to relate instances of that class to objects of the related class. As new instances are created dynamically, actual relationships between those instances must also be dynamically created and deleted. Relationships are created and deleted explicitly by the application or implicitly by Objectivity/DB when objects are copied or versioned.
You must do two things to define a relationship:
The relationship field must be of one of the following types:
This definition illustrates how the salesperson-contacts example discussed earlier would be defined in Objectivity for Java. It shows a class named Salesperson whose instances can be related to many objects. The relationship is implemented by an instance of ToManyRelationship identified by the name contacts. Note that as with other fields of objects, the recommended practice is to make the relationship field private, and provide public methods for exposing the relationship behavior.
class Salesperson { ... private ToManyRelationship contacts; ... }
The relationship definition method must return a properly initialized instance of a subclass of Relationship (OneToOne, OneToMany, ManyToOne, or ManyToMany) that corresponds to the cardinality of the relationship. The name of the method must be formed by appending _Relationship to the name of the relationship field.
Each of the Relationship subclasses provides several constructors for creating the relationship instance. The parameters specify whether the relationship is unidirectional or bidirectional, copy and version behavior, operation propagation options, and storage mode.
The first two parameters of all constructors are the name of the local relationship field and the name of the related class. If the relationship is bidirectional, the third parameter must be the name of the inverse relationship field in the related class. The next four parameters specify how objects are affected when the object to which they are related is locked, deleted, copied, or versioned. You must specify either all of the behavior specifiers or none of them. For example, if you want to specify lock propagation, you must also specify delete propagation, copy mode, and version mode. The constants used to specify the various behaviors are defined in the Relationship class. Finally, the last parameter specifies the storage mode; storage mode constants are also defined in the Relationship class. This parameter can be omitted, in which case the relationship is stored non-inline.
ExampleThe relationship between a Contact and a Salesperson is defined to be bidirectional, thus requiring relationship definitions in both the Salesperson and Contact classes. The following definition also implements our earlier decision to disable the propagation of locking and deletion operations. If any Salesperson or Contact objects were copied or versioned, relationships with any respective Contact or Salesperson objects would be moved to a new Salesperson object and copied to a new Contact object. Both relationships would be stored non-inline.
package Sales; import com.objy.db.app*; class Salesperson extends ooObj { private ToManyRelationship contacts; public static OneToMany contacts_Relationship() { return new OneToMany( "contacts", // Relationship field "Sales.Contact", // Related class "salesperson", // Inverse Relationship.COPY_MOVE, // Copy behavior Relationship.VERSION_MOVE, // Version behavior false, // Delete propagation false, // Lock propagation Relationship.INLINE_NONE); // Store non-inline }
package Sales; import com.objy.db.app*; class Contact extends ooObj { private ToOneRelationship salesperson; public static ManyToOne salesperson_Relationship() { return new ManyToOne( "salesperson" // Relationship field "Sales.Salesperson", // Related class "contacts", // Inverse Relationship.COPY_COPY, // Copy behavior Relationship.VERSION_COPY, // Version behavior false, // Delete propagation false, // Lock propagation Relationship.INLINE_NONE); // Store non-inline } }
To create and delete relationships between objects and navigate between related objects, you use the methods defined by the class, ToOneRelationship or ToManyRelationship, of the object referenced by the relationship field.
Before you can call any of these methods, however, the object containing the relationship field must be persistent and the session that owns it must be in a transaction. While the object is transient, the relationship field is uninitialized and any attempt at accessing it will throw a NullPointerException. On the other hand, a transient object can be passed as a parameter to one of the methods that form a relationship, and it will be made persistent as part of the operation. In that case, the default clustering strategy clusters the related object into the container where the relating object is stored. Once the related object has been made persistent, it belongs to the same session as the relating object.
Once you have defined a relationship on a class and created a persistent instance of that class, you can:
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 get related objects or establish relationships with other objects.
The Contact class provides the typed setSalesperson method to create a relationship between a Contact object and a Salesperson object. A Contact object can get its related Salesperson object with the public getSalesperson method, which casts the object returned from the ToOneRelationship.get method to the correct type. The complete class definition appears in the Sales.Contact programming example .
package Sales; class Contact extends ooObj { ... public void setSalesperson( Salesperson newSalesperson) { fetch(); // Remove any existing relationship this.salesperson.clear(); this.salesperson.form(newSalesperson); } public Salesperson getSalesperson() { fetch(); // Cast retrieved object to class Salesperson return (Salesperson)this.salesperson.get(); }
The findContactsByCompany method of the Salesperson class illustrates how to scan a to-many relationship for the objects that satisfy a predicate condition. The complete class definition appears in the Sales.Salesperson programming example .
package Sales; class Salesperson extends ooObj { ... public Iterator findContactsByCompany ( String company) { fetch(); String predicate = new String("company == \"\"" + company + "\""); return this.contacts.scan(predicate); } }
When an object is first created or when an object is first read from the database, all its relationship fields have null values. To fill in the relationship fields, fetch() must be called. Once fetched, the fields, which hold objects typed either as ToOneRelationship or ToManyRelationship, exist for the lifetime of the containing object. Developers must never set the relationship fields or assign their values to other objects. This is why the fields are shown as private in all the examples.
Throughout the lifetime of a persistent object, the relationship fields get set to different values at runtime by Objectivity for Java. This can occur at these events:
Objectivity for Java obtains read and write locks as required when using the relationship objects. The following table lists the operations and the lock types for each type of relationship.
Relationships are maintained in the database and not in Java memory. This alleviates garbage collection issues.
Guide  Reference  Contents  Previous  Next  Index