Working with Transactions

 

Author: Eike Stepper

A transaction is a read-write view on the current state of a repository branch. It shares the view concepts explained in Working with Views, but additionally records local model changes until they are committed or rolled back.

A root transaction commit is the operation that persists the effective changes in the repository. Savepoints and nested scopes are client-side composition mechanisms; completing either one does not create a repository commit. Server applications that validate or observe these commits use the supported handlers described in Repository Handlers and Commit Processing.

Table of Contents

1 Creating and Managing Transactions
2 Local Changes and Dirty State
3 Committing Changes
4 Commit Retry and Conflicts
5 Rolling Back Changes
6 Partial Commits
7 Savepoints
8 Nested Transaction Scopes
9 Merge
10 Revert
11 Change Export and Import
12 File-Backed Transactions
13 Queries in Transactions
14 Transaction Options

1  Creating and Managing Transactions

Transactions are opened from a session. The session supplies the repository connection and the transaction owns its view and resource set until it is closed. The common overload of openTransaction() and the more specific overloads inherited from CDOTransactionContainer allow an application to select a branch point, resource set, or durable-locking identity.

A transaction remains usable after a successful commit, so an application can perform another unit of work or close it. Closing the transaction releases its client-side resources; it does not replace commit or rollback.

OpenTransaction.java      
CDOTransaction transaction = session.openTransaction();

try
{
  Resource resource = transaction.getOrCreateResource("/example");
  // Modify the resource or its model contents here.

  transaction.setCommitComment("Update example model");
  transaction.commit();
}
finally
{
  transaction.close();
}

2  Local Changes and Dirty State

A transaction is dirty when it contains uncommitted changes. The public transaction API exposes separate maps for new, detached, and modified objects through CDOTransaction.getNewObjects(), CDOTransaction.getDetachedObjects(), and CDOTransaction.getDirtyObjects(). CDOTransaction.getRevisionDeltas() exposes the revision-level changes.

These collections describe the transaction's current local state. They are not a second repository history and are cleared or reduced as changes are committed or rolled back.

InspectDirtyState.java      
if (transaction.isDirty())
{
  int newObjects = transaction.getNewObjects().size();
  int dirtyObjects = transaction.getDirtyObjects().size();
  int detachedObjects = transaction.getDetachedObjects().size();

  System.out.println("New: " + newObjects);
  System.out.println("Dirty: " + dirtyObjects);
  System.out.println("Detached: " + detachedObjects);
}

3  Committing Changes

CDOTransaction.commit() sends the transaction's effective changes to the repository and returns a commit-info object. The transaction stays open after a successful commit. Commit comments and arbitrary commit properties can be set with CDOTransaction.setCommitComment(String) and CDOTransaction.setCommitProperty(String, String); committed metadata can be observed through the commit-info manager.

A commit can fail with CommitException, including a CommitConflictException or an OptimisticLockingException. Applications must not assume that acquiring explicit locks eliminates every possible commit failure.

CommitChanges.java      
transaction.setCommitComment("Update customer model");
transaction.setCommitProperty("source", "customer-editor");
return transaction.commit();

4  Commit Retry and Conflicts

CDOTransaction.hasConflict() and CDOTransaction.getConflicts() expose objects whose local modifications conflict with remote changes. A configured conflict resolver can resolve conflicts during invalidation; otherwise an application commonly rolls back, reapplies its business operation against the current view, and commits again.

The retry overloads of CDOTransaction.commit(Runnable, int, IProgressMonitor) and its Callable counterpart run the operation before each attempt. The integer is the total attempt count, so 3 permits one initial attempt and two retries. When a ConcurrentAccessException occurs, CDO rolls back the failed attempt before trying again; other commit failures are not retried by this overload. The operation supplied for retry must therefore be repeatable and must reapply the intended changes.

CommitWithRetry.java      
transaction.commit(operation, 3, null);

5  Rolling Back Changes

CDOTransaction.rollback() removes all uncommitted changes from the root transaction and leaves the transaction open for further work. It is different from rolling back a savepoint, which retains the earlier part of the transaction, and from rolling back a scope, which affects only that scope and its descendants.

RollbackChanges.java      
if (transaction.isDirty())
{
  transaction.rollback();
}

6  Partial Commits

A transaction can restrict one commit to a set of committable objects with CDOTransaction.setCommittables(Set). CDO filters the transaction's new, dirty, and detached objects against this set; it does not automatically compute a dependency closure. If a selected object refers to a new object that is not selected, the intended commit may be incomplete or violate model constraints. Include the full set needed for a valid repository change. Non-selected changes remain local and the transaction stays dirty. Use partial commits only when those staged boundaries make sense to the domain; independent business units are usually easier to reason about as separate transactions.

CommitSelectedObjects.java      
transaction.setCommittables(committables);
transaction.commit();

7  Savepoints

CDOTransaction.setSavepoint() creates an in-memory client-side boundary in the transaction's change history. CDOUserSavepoint.rollback() restores the transaction to that boundary by undoing changes made after it, while keeping the root transaction active. Savepoints do not flush changes to disk or to the repository.

CDOSavepoint is the richer transaction-specific view of the same boundary. It exposes the objects and revision deltas belonging to a savepoint, including CDOSavepoint.getDirtyObjects() and CDOSavepoint.getAllChangeSetData(). Use those inspection APIs only when the application needs to reason about the change segment itself.

UseSavepoint.java      
CDOUserSavepoint savepoint = transaction.setSavepoint();

try
{
  operation.run();
  if (!accept)
  {
    savepoint.rollback();
  }
}
catch (RuntimeException ex)
{
  savepoint.rollback();
  throw ex;
}

8  Nested Transaction Scopes

CDOTransaction.openScope() creates a stack-disciplined scope inside the root transaction. A scope shares the transaction's view, resource set, object identities, cache, dirty state, locks, and session. Its changes are immediately visible in the containing transaction.

CDOTransactionScope.commit() accepts the scope into its parent but never persists anything to the repository. CDOTransactionScope.rollback() restores the state at the scope boundary, and CDOTransactionScope.close() rolls back an active scope. Only a later commit on the root CDOTransaction creates the repository commit. Scopes may be nested and must be completed from the innermost scope outward.

CDOTransactionScope.asTransaction() supplies a stable nested transaction facade for APIs that accept a transaction. Commit operations on that facade are unsupported; the scope itself is completed with CDOTransactionScope.commit().

RunBusinessOperationInScope.java      
try (CDOTransactionScope scope = transaction.openScope())
{
  operation.accept(scope.asTransaction());
  scope.commit();
}

9  Merge

CDOTransaction.merge(CDOBranch, CDOMerger) and the related branch-point overloads apply changes from a source branch or branch point to the local transaction. They create local changes; the caller still decides when to commit them. The returned CDOChangeSetData describes the applied change set. Merge conflicts are part of the transaction conflict model and must be resolved before a successful commit.

Branch selection and historical branch points belong in the Branching and Versioning chapter. This section is limited to the transaction side of applying and committing a merge.

10  Revert

CDOTransaction.revertTo(CDOBranchPoint) creates local changes that restore the transaction's model to a specified historical branch point. Revert is not the same as rollback: rollback discards uncommitted local work, whereas revert prepares a new change set that can itself be reviewed and committed. It is also distinct from a savepoint rollback and from opening a historical read-only view.

11  Change Export and Import

CDOTransaction.exportChanges(OutputStream) serializes the transaction's local changes to an output stream and CDOTransaction.importChanges(InputStream, boolean) applies serialized transaction changes from an input stream. The boolean controls whether savepoints are reconstructed while importing. These operations work with transaction changes; they are not repository commits, raw revision history exports, or generic model serialization.

The file-backed transaction described below uses these APIs internally, but they can also be used directly when an application controls the transfer stream.

TransferChanges.java      
source.exportChanges(output);
target.importChanges(input, true);

12  File-Backed Transactions

CDOFileTransaction is the current public API for persisting uncommitted changes in its stable backing file (available through CDOFileTransaction.getFile()) and later pushing them to the repository. Its normal CDOFileTransaction.commit() persists the current uncommitted changes to that file and does not commit to the repository; CDOFileTransaction.push() performs the repository commit and removes the persisted file after success. The inherited rollback operation is unsupported, as are the inherited callable and runnable commit overloads. File-backed transactions are therefore appropriate for explicit export-and-push workflows rather than ordinary rollback-based editing.

The older CDOPushTransaction API is deprecated as of 4.30 in favor of CDOFileTransaction and is not used in this example.

UseFileBackedTransaction.java      
CDOFileTransaction fileTransaction = CDOUtil.createFileTransaction(transaction);

try
{
  // Modify the delegate transaction here.
  fileTransaction.commit();

  // The file can be retained across application restarts before this step.
  fileTransaction.push();
}
finally
{
  fileTransaction.close();
}

13  Queries in Transactions

General querying is covered in the Views chapter. A transaction additionally offers CDOTransaction.createQuery(String, String, boolean) and its context overload, whose considerDirtyState argument controls whether CDO adds the transaction's local change-set data to the query request. This lets query implementations that support change-set data account for new, modified, and detached objects alongside repository results. It does not execute an arbitrary query over a complete in-memory copy, and a custom query handler must honor the supplied change-set data for the option to affect its results. Use true when asking about the transaction's working state; the default repository-only query answers a different question.

QueryDirtyState.java      
return transaction.createQuery("ocl", "EObject.allInstances()", true);

14  Transaction Options

CDOTransaction.options() exposes the transaction-specific options in addition to the view options described in the Views chapter. Application developers should normally consider three groups: conflict resolvers for automatic handling of remote conflicts; optimistic-locking and commit-info timeouts for bounded commit behavior; and automatic lock release, including its exemptions, for predictable lock ownership after commit or rollback.

The options API is intentionally linked rather than duplicated here. Its Javadoc documents defaults and the complete option set, including undo detection, stale-reference cleaning, and attached-revision handling.

ConfigureTransactionOptions.java      
transaction.options().setOptimisticLockingTimeout(10000L);
transaction.options().setCommitInfoTimeout(10000L);
transaction.options().setAutoReleaseLocksEnabled(true);