There is an implied contract between persistent objects and Cayenne runtime. Cayenne expects persistent objects to follow certain conventions, while itself providing management of the various aspects of a persistent object graph.

Persistent Object Requirements

Persistent Interfaces

Cayenne can persist Java objects that implement org.objectstyle.cayenne.Persistent interface. The interface requires for an object to provide getters and setters for three bean properties: objectId, persistenceState and objectContext:

Persistent.java
public interface Persistent extends Serializable {
    ObjectId getObjectId();

    void setObjectId(ObjectId id);

    int getPersistenceState();

    void setPersistenceState(int state);

    ObjectContext getObjectContext();

    void setObjectContext(ObjectContext objectContext);
}

Furthermore the most commonly used implementation of ObjectContext - DataContext - requires a more complicated subinterface of Persistent - org.objectstyle.cayenne.DataObject, that specifies generic methods for property access. The easiest way to satisfy these requirements is by using class generation mechanism provided by Cayenne (using cgen Ant task or CayenneModeler UI).

It is worth noting that both requirements will likely become optional in the future releases, being substituted with reflection, bytecode enhancements and other such techniques. Still it is important to understand both benefits and shortcomings of the persistent interface requirement.

The obvious (and only) shortcoming is that the users have to implement it, most often using a class generation template that relies on a framework superclass (such as org.objectstyle.cayenne.CayenneDataObject). This may somewhat limit the flexibility of the application design.

In returns users (and Cayenne framework internally) get extra capabilities:

  • Fast and consistent mechanism for the framework to inspect, cache, manipulate the objects.
  • Meaningless primary key doesn't have to be an object property.
  • An object always knows its context, and thus can access the database from its business logic methods without any external context.
  • An object always knows how its state compares to the state of the backing database row, and can implement logic based on that knowledge (e.g. objects that are modified, but not yet committed, can be shown in a different color in the user interface).
  • DataObject interface makes possible generic persistent objects, i.e. the same generic class can map to more than one entity, and persistent behavior can be defined dynamically in runtime.

Property Accessors

Another convention, that is not required strictly speaking, but is almost always implemented by persistent objects is invoking a callback method on their enclosing context before reading or setting their properties. Intercepting property accessors enables lazy on-demand resolution of objects and their relationships and also automatic bidirectional relationships, as discussed below. As with Persistent interface, property interception code is usually created via class generation.

Handling Persistent Objects

Cayenne part of the "persistence contract" is the services it provides, including persistence per se and persistence-related object graph management capabilities.

Query Capabilities

Queries can be executed, bringing back objects matching certain criteria. As a part of this procedure, persistent objects are created and inflated with database values.

Single Method Call Commit and Rollback

Multiple persistent object changes can be committed with a single method call (and in a single transaction). Similarly, object graph changes made since last commit can be discarded with a single method call.

Multiple Levels of Commit and Rollback Nesting

Commit and rollback functionality can have multiple levels of nesting (i.e. a context can rollback its changes without affecting the parent context; or commit its changes to parent without committing them all the way to the database).

Relationships

Relationship support - objects related to the previously fetched objects can be accessed via a simple method call. Cayenne will do whatever is necessary to resolve related objects at the right moment behind the scenes.

Unless the user specifies otherwise in the query that fetched the initial objects, relationships are not fetched together with the objects. When a user requests a related object (or collection of objects), Cayenne ensures that the actual database query to read it is deferred as much as possible, so hopefully there won't be a need to do it at all. E.g. a to-many relationship is resolved only when a list is queried for its size, or a user tries to access one of the elements.

Automatic Bi-directional Relationship Managemenet

If entity A has a relationship to entity B and entity B has a relationshop back to entity A, Cayenne would maintain consistency of the reverse relationship automatically. Consider this example of a many-to-one relationship, written in a form of a unit test:

A a1;
B b1;
B b2;

a1.setB(b1);
assertTrue(b1.getListOfA().contains(a1));

a1.setB(b2);
assertTrue(b2.getListOfA().contains(a1));
assertFalse(b1.getListOfA().contains(a1));

This significantly simplifies coding and reduces possibility of errors in managing complex object graphs.

Context Injection

Cayenne framework injects all three properties defined in Persistent interface - objectId, persistenceState and objectContext - at the right moments in the lifecycle. It automatically maintans persistence state changes when an object undergoes state transformations.

Uniquing

Cayenne ensures that each ObjectContext contains at most one instance of each unique persistent object. In other words if two separate independent queries fetched a row with the same primary key, the same object instance will be used in both results. This behavior (not supported by some other frameworks), is extremely important in maintaining consistency of the object graph.

Lazy Object Resolution

One of the object states is HOLLOW, corresponding to unresolved objects that only have their PK known. Most often HOLLOW objects are returned from to-one relationships. Whenever such object is "touched" by the user (i.e. a setter or a getter is invoked), Cayenne automatically infaltes it with the database values.