Arguably the second most important function of DataContext (first is performing queries) is keeping track of changes made to the registered DataObjects. "Registered" is a keyword here - registering an object with DataContext is what gives this object its persistent qualities.

How to Register a DataObject

Behind the scenes "registering an object" results in storing this object in a map using its ObjectId as a key, setting "dataContext" property of a DataObject to the current DataContext, and taking a snapshot of all persistent properties to be able to track later modifications. Objects can become "registered" in two ways:

  • automatically when they are fetched via query API
  • explicitly for the newly created objects

Whenever a selecting query is executed by a DataContext, all fetched objects are automatically registered with this DataContext. On the other hand, newly created objects must be registered explicitly:

import org.objectstyle.cayenne.access.DataContext;
...
DataContext context; // assume this exists

Artist artist = new Artist();
context.registerNewObject(artist);

// after the line above is executed, artist object acquired "persistent" behavior
// and is said to be "managed" by DataContext

This code can be simplified - object creation and registrations steps can be combined in one method call:

import org.objectstyle.cayenne.access.DataContext;
...
DataContext context; // assume this exists

Artist artist = (Artist) context.newObject(Artist.class);

This method relies on the presence of a no-argument constructor in the DataObject class.

Checking the State of Registered DataObjects

State transitions of DataObjects from persistence point of view are discussed in the "Design" chapter. State of each individual object is described by an integer constant obtained via a call to DataObject.getPeristenceState(). Allowed states are defined as static variables in PersistenceState class.

When a new object is inserted to the DataContext as described above, it becomes "NEW":

import org.objectstyle.cayenne.access.DataContext;
...
DataContext context; // assume this exists

// artist will become PersistenceState.NEW
Artist artist = (Artist) context.newObject(Artist.class);

When a DataContext is committed, such object becomes "COMMITTED":

// artist will become PersistenceState.COMMITTED
context.commitChanges();

When any of the attributes or relationships of the fetched or committed object are changed, such an object becomes MODIFIED:

// this will change the object state to PersistenceState.MODIFIED
artist.setName("NewName");

When a fetched or committed object is explicitly deleted from the DataContext, object becomes DELETED:

// this will change the object state to PersistenceState.DELETED
context.deleteObject(artist);

DataContext is said to have changes if it has one or more registered objects in a state PersistenceState.MODIFIED, PersistenceState.NEW or PersistenceState.DELETED. DataContext provides the following method to check if it has any changed objects:

  • public boolean hasChanges()

There is also a way to obtain a list of changed objects in each one of the above states:

  • public java.util.Collection newObjects()
  • public java.util.Collection deletedObjects()
  • public java.util.Collection modifiedObjects()

Saving All Uncommitted DataObjects

All of the uncommitted objects ("uncommitted" means "new", "modified" or "deleted") are saved ("committed") to the database with a single method call on the DataContext:

  • public void commitChanges()

Method commitChanges takes care of building correct SQL statements, generating primary keys and transactional behaviour. It roughly follows this scenario:

  • Checks if there are any changed objects (also detecting "phantom" modifications, e.g. if an object property was "updated" with the equivalent value).
  • Validates "dirty" objects (for more information on validation see this page).
  • Generates primary keys for any NEW objects that require autogenerated key.
  • Builds any needed INSERT, UPDATE, DELETE queries.
  • Starts the database transaction.
  • Runs the queries.
  • Commits transaction.
  • Changes all committed objects state to PersistenceState.COMMITTED.
  • Updates internally stored snapshots of the recently saved objects.

Undoing All Uncommitted Changes

There is a way for the DataContext to undo all uncommitted changes:

  • public void rollbackChanges()

This will restore the persistence state and the values of all registered objects to the values that objects had when they were fetched or the last commitChanges was executed. This effectively restores previously committed state of the object graph. Note that any NEW objects are unregistered from the context.