m(→‎How to Use the addConstraintDependencies Method of the Descriptor)

Line 686:

Line 686:

# You can use the <tt>deleteAllObjects</tt> method without the <tt>addConstraintDependencies</tt> method (see [[#How to Use the deleteAllObjects Method Without the addConstraintDependencies Method|How to Use the deleteAllObjects Method Without the addConstraintDependencies Method]])

# You can use the <tt>deleteAllObjects</tt> method without the <tt>addConstraintDependencies</tt> method (see [[#How to Use the deleteAllObjects Method Without the addConstraintDependencies Method|How to Use the deleteAllObjects Method Without the addConstraintDependencies Method]])

−

# You can use the <tt>deleteAllObjects</tt> method with the <tt>addConstraintDependencies</tt> method (see [[#How to Use the deleteAllObjects Method with the addConstraintDependencies Method|How to Use the deleteAllObjects Method Without the addConstraintDependencies Method]])

+

# You can use the <tt>deleteAllObjects</tt> method with the <tt>addConstraintDependencies</tt> method (see [[#How to Use the deleteAllObjects Method with the addConstraintDependencies Method|How to Use the deleteAllObjects Method With the addConstraintDependencies Method]])

−

+

===How to Use the deleteAllObjects Method Without the addConstraintDependencies Method===

===How to Use the deleteAllObjects Method Without the addConstraintDependencies Method===

For more information about the available methods for the UnitOfWork, see EclipseLink API Reference.

Registering and Unregistering Objects

The unit of work provides a number of object registration options.

How to Create and Register a New Object in One Step Using UnitOfWork Method newInstance

The following example shows how to use the unit of work newInstance method to create a new Pet object, register it with the unit of work, and return a clone, all in one step. If you are using a factory design pattern to create your objects (and have specified this in the query builder), the newInstance method will use the appropriate factory.

How to Use the registerAllObjects Method

The registerAllObjects method takes a Collection of objects as an argument and returns a Collection of clones. This lets you register many objects at once, as the following example shows.

Note: You cannot use UnitOfWork methods registerObject, registerNewObject, or registerExistingObject with an aggregate object. Doing so will raise a ValidationException or other errors at commit time. For more information, see How to Work with Aggregates.

How to Use Registration and Existence Checking

When you register an object with the unit of work, EclipseLink runs an existence check to determine whether or not the object exists. EclipseLink uses this information at commit time to determine whether to perform an insert or an update operation. You can specify the default existence checking policy for a project as a whole or on a per-descriptor basis. By default, EclipseLink uses the check cache existence checking policy. If you use any existence checking policy other than check cache, then you can use the way you register your objects to your advantage to reduce the time it takes EclipseLink to register an object.

Using Check Database

If you configure a class's descriptor with an existence checking policy of check database, EclipseLink will check the database for existence for all instances of that class registered in a unit of work. However, if you know that an object is new or existing, rather than use the basic registerObject method, you can use registerNewObject or registerExistingObject to bypass the existence check. EclipseLink will not check the database for existence on objects that you have registered with these methods. It will automatically perform an insert operation if registerNewObject is called, or an update operation if registerExistingObject is called.

Using Assume Existence

If you configure a class's descriptor with an existence checking policy of assume existence, EclipseLink will assume that all instances of that class registered with a unit of work exist and EclipseLink will always perform an update operation to the database on all such registered objects; even new objects that you registered with registerObject method. However, if you use the registerNewObject method on the new object, EclipseLink knows to perform an insert operation in the database even though the existence checking policy says assume existence.

Using Assume Nonexistence

If you configure a class's descriptor with an existence checking policy of assume nonexistence then EclipseLink assumes that all instances of that class registered with a unit of work do not exist and will always perform an insert operation on the database, even on objects read from the database. However, if you use the registerExistingObject method on existing objects, EclipseLink knows to perform an update operation on the database.

How to Work with Aggregates

Aggregate mapped objects should never be registered in an EclipseLink unit of work–doing so will generate an exception. Aggregate cloning and registration is automatic based on the owner of the aggregate object. In other words, if you register the owner of an aggregate, the aggregate is automatically cloned. When you get a working copy of an aggregate owner, its aggregate is also a working copy.

When working with aggregates, you should always use an aggregate within the context of its owner:

If you get an aggregate from a working clone owner, then the aggregate is a working clone.

If you get an aggregate from a cache version owner, then the aggregate is the cache version.

How to Unregister Working Clones

The unit of work unregisterObject method lets you unregister a previously registered object from a unit of work. An unregistered object will be ignored in the unit of work, and any uncommitted changes made to the object up to that point will be discarded.

In general, this method is rarely used. It can be useful if you create a new object, but then decide to delete it in the same unit of work (which is not recommended).

New object can be queried prior to commit (without using conforming query).

Calling registerObject on child is an error.

Modify existing object known to exist in the database.

registerExistingObject

Performance enhancement: no call to Descriptor method doesExist.

Modify a Collection of objects

registerAllObjects

Convenience method: equivalent to calling registerObject for each object in the Collection.

If a new object is reachable from a clone, you do not need to register it.

When working with new objects, remember the following:

Only reachable or registered objects will be persisted.

Reachable new objects or objects that have been registered with registerNewObject are considered to be working copies in the unit of work.

If you call registerObject with a new object, the clone the method returns, and the argument you pass in, are considered the cache version.

The registerNewObject method registers a new object as if it was a clone. At commit time, the unit of work creates another instance of the object to be the cache version of that object. Use the registerNewObject method in situations where the following applies:

You do not need a handle to the cache version of the object after the commit transaction and you do not want to work with clones of new objects.

You must pass a clone into the constructor of a new object, then register the new object.

Note that if you call registerNewObject on an object, EclipseLink does not cascade registration to new children of that object. Children will be persisted but you cannot query them before commit (unless you use conforming queries).

To make children visible to queries before commit, you must do one of the following:

register the parent using registerObject

register each child using registerNewObject if you register the parent using registerNewObject

Declaring Read-Only Classes

You can declare a class as read-only within the context of a unit of work. Clones are neither created nor merged for such classes, thus improving performance. Such classes are ineligible for changes in the unit of work.

When a unit of work registers an object, it traverses and registers the entire object tree. If the unit of work encounters a read-only class, it does not traverse that branch of the tree, and does not register objects referenced by the read-only class, so those classes are ineligible for changes in the unit of work. The read-only classes are cached and must not be changed by the user.

How to Configure Read-Only Classes for a Single Unit of Work

For example, suppose class A owns a class B, and class C extends class B. You acquire a unit of work in which you know only instances of class A will change: you know that no class Bs will change. Before registering an instance of class B, use the following:

myUnitofWork.addReadOnlyClass(B.class);

You can then proceed with your transaction: registering class A objects, modifying their working copies, and committing the unit of work.

At commit time, the unit of work will not have to compare backup clones with the working clones for instances of class B (even if instances were registered explicitly or implicitly). This can improve unit of work performance if the object tree is very large.

Note that if you register an instance of class C, the unit of work does not create or merge clones for this object; any changes made to class C are not be persisted because class C extends class B and class B was identified as read-only.

To identify multiple classes as read-only, add them to a Vector and use the following code:

myUnitOfWork.addReadOnlyClasses(myVectorOfClasses);

Note that a nested unit of work inherits the set of read-only classes from the parent unit of work. For more information on using a nested unit of work, see Using a Nested or Parallel Unit of Work.

How to Configure Default Read-Only Classes

To establish a default set of read-only classes for all units of work, use the project method setDefaultReadOnlyClasses(Vector). After you call this method, all new units of work include the Vector of read-only classes.

How to Declare Read-Only Descriptors

When you declare a class as read-only, the read-only declaration extends to its descriptors. You can declare a descriptor as read-only at development time, using either Java code or Workbench. This option improves performance by excluding the read-only descriptors from unit of work registration and editing.

To flag descriptors as read-only in Java code, call the setReadOnly method on the descriptor as follows:

descriptor.setReadOnly();

To declare a descriptor as read-only in Workbench, select the Read Only check box for the specific descriptor.

Writing Changes Before Commit Time

By default, when you call the unit of work commit method, EclipseLink writes your changes to the data source and commits your changes.

Alternatively, you can perform a two-stage or partial commit transaction by using the unit of work writeChanges method prior to calling commit (either directly or by way of an external transaction service).

When you call the unit of work writeChanges method, the unit of work commit process begins, and all changes are written out to the data source. However, the data source transaction is not committed, nor will changes be merged into the shared session cache. To finalize your changes, the unit of work commit method must still be called (either directly or by way of an external transaction service).

After you call the unit of work writeChanges method, any attempt to register objects or execute object-level queries will throw an exception. You may execute report queries, noncaching queries, and data read and modify queries.

If any exception is thrown, the transaction will be rolled back (or marked rollback only) and you cannot recover the unit of work.

You can call this method only once. You cannot use this method to write out changes in an incremental fashion.

You can use the unit of work writeChanges method to address a variety of transaction issues, including the following:

Using Conforming Queries and Descriptors

Because queries are executed on the database, querying though a unit of work will not, by default, include new, uncommitted objects in a unit of work. The unit of work will not spend time executing your query against new, uncommitted objects in the unit of work unless you explicitly tell it to. If you have uncommitted changes, this can pose a problem in a unit of work. Uncommitted changes not yet written to the database cannot influence which result set gets returned.

Conforming is a query feature that lets you include new, changed, or deleted objects in queries within a unit of work prior to committing. This lets you to query against your relative logical or transaction view of the database.

Note: By default, EclipseLink suppresses exceptions thrown during the memory search stage of conforming. For more information on handling exceptions during conforming, see Exceptions During Conforming.

How to Use Conforming

When using conforming, follow the guidelines that this section describes to ensure that conforming queries return the correct results:

Ensuring that the Query Supports Conforming

Query by selection object or primary key (only new or deleted objects apply)

Conforming is not supported by the following queries:

ReportQuery

DataReadQuery

DataReadQuery (deleted objects can be conformed)

StoredProcedureCall (deleted objects can be conformed)

EISCall (deleted objects can be conformed)

Expression or EJB QL queries that use database-specific functions, or subselects

Parallel expressions (queries across independent entity types)

Considering how Conforming Affects Database Results

When conforming is used on a ReadAllQuery, the database result is first queried. If the unit of work has not yet committed any changes to the database, this result will not reflect the unit of work changes. The database results are then conformed in memory using the following criteria:

Registered new objects that conform to the query are added.

Modified existing objects that no longer conform are removed.

Modified existing objects that conform are added.

Deleted objects are removed.

Note: If new objects are not explicitly registered, they are not conformed. Also, if removed object are not explicitly deleted, they are not conformed.

If the query uses ordering, ordering of conformed results is not maintained and conformed instances are added to the front of the result. To apply ordering, store the result in memory using Collections method sort, or a TreeSet result collection class. When using conforming on a ReadObjectQuery, first query the unit of work for a conforming object: if the conforming object is found, it is returned and the database access is avoided; if the conforming object is not found, the database is queried. If the unit of work has not yet committed any changes to the database, this result does not reflect the unit of work changes. The database results are then conformed in memory using the following criteria:

If the database result no longer conforms, null is returned.

If the database result has been deleted, null is returned.

Note: If the database result returns multiple results, only the first result is considered, because it is an instance of the ReadObjectQuery and only a single result is expected. If the first result no longer conforms, null is returned, even if there were potential valid conforming results. If you expect the query to return multiple results, use a ReadAllQuery.

Registering New Objects and Instantiate Relationships

If a new object is only related to an existing object, and not explicitly registered, queries for this object are not able to conform it. If you remove, but do not explicitly delete a privately owned object, this object cannot be conformed.

If a query traverses relationships (uses joins) and the related objects are changed, the query can only conform these objects if both of the following conditions are met:

The source objects have been registered in the unit of work.

The source objects' relationship has been instantiated.

EclipseLink provides a conforming option that forces an instantiation of indirection (lazy loading). However, you use this option with caution as it can cause an increased database access.

You want to read all employees who live in Ottawa, but first, you need to modify some of the Address objects to change city from Toronto to Ottawa. Jane Doe is one such employee.

First, using the UnitOfWork, you read all Address objects and change some city attributes (including Jane's) from Toronto to Ottawa. Then you run a conforming query to get all employees who live in Ottawa. However, for the following reasons Jane is not included in the results, even though she now lives in Ottawa:

Jane is not returned from the database because the transaction has not yet been committed and in the database, her address still says Toronto.

Jane cannot be added to the conformed result in memory because she is not registered in the UnitOfWork cache.

Conforming only recognizes explicit changes. In this example, Jane Doe's Employee object was only implicitly changed. In order to be considered explicitly changed, an Employee must meet the following criteria:

Be registered in the UnitOfWork.

Have its address attribute changed: in this example, indirection (lazy loading) must be triggered for the address attribute.

The correct way to handle this example would be as follows:

Using the UnitOfWork, read in all employees.All these Employee objects are now registered with the UnitOfWork

Using the same UnitOfWork, access the employees' addresses, instantiating the indirect relationships.

Modify the employees' addresses, changing some of the addresses to be in Ottawa.

Run the conforming query on employees with addresses inside Ottawa.All employees with addresses in Ottawa are returned, including both employees that were in Ottawa originally and employees whose addresses were changed in this transaction.

Commit the transaction.

If you do not register all employees whose address may be changed, and instantiate their address relationship, the conforming query will not include Jane.

An alternate approach is to use short transactions: the safest conforming query is one made immediately after a commit. For example:

Using the UnitOfWork, read in all addresses outside of Ottawa.

Modify the addresses, changing some of the addresses to be in Ottawa

Commit the transaction.

Using the UnitOfWork, read in all employees inside Ottawa.

How to Use Conforming Queries

Assume that a single Pet of type Cat already exists on the database. Examine the code shown in this example.

How to Use Conforming Descriptors

EclipseLink's support for conforming queries in the unit of work can be specified at the descriptor level.

You can define a descriptor directly to always conform results in the unit of work so that all queries performed on this descriptor conform its results in the unit of work by default. You can specify this either within code or from the Workbench.

You can configure a descriptor to always conform in the unit of work using the Workbench or Java code.

To configure a descriptor to always conform in the unit of work in Java code, use Descriptor method setShouldAlwaysConformResultsInUnitOfWork, passing in an argument of true.

What You May Need to Know About Conforming Query Alternatives

This section describes alternatives to conforming that may meet your needs without the performance penalty imposed by conforming.

Using Unit of Work Method writeChanges Instead of Conforming

Using UnitOfWork method writeChanges, you can write uncommitted changes to the data source: you can execute report queries, noncaching queries, and data read and modify queries against these changes (see the following example).

Using Unit of Work Properties Instead of Conforming

Sometimes, you need to provide other code modules with access to new objects created in a unit of work. Conforming can be used to provide this access. However, the following alternative is significantly more efficient.

Somewhere a unit of work is acquired from a session and is passed to multiple modules for portions of the requisite processing:

In other modules where newPet needs to be accessed for further modification, it can simply be extracted from the unit of work's properties:

Pet newPet = (Pet) uow.getProperty("NEW PET");
newPet.setType("Dog");

Conforming queries are ideal if you are not sure if an object has been created yet or the criteria is dynamic.

However, for situations where the quantity of objects is finite and well known, using unit of work properties is a simple and more efficient solution.

Merging Changes in Working Copy Clones

In a three-tier application, the client and server exchange objects using a serialization mechanism such as RMI or CORBA. When the client changes an object and returns it to the server, you cannot register this serialized object into a unit of work directly. On the server, you must merge the serialized object with the original object in the session cache.

Using the unit of work methods listed in the Unit of Work Merge Methods table, you can merge a deserialized object into your session cache. Each method takes the serialized object as an argument and returns the original object.

Before doing so, you must ensure that the source object is in your session cache. Attempting to merge a deserialized object into a session cache that does not yet contain the object will result in a descriptor exception. To avoid this, we recommend that you first read the object instance that the deserialized object represents. If you are using a coordinated cache or your application is running in a cluster, the session you merge into may not yet contain your original object. By performing a read operation first, you guarantee that the object will be in the cache before you merge.

Unit of Work Merge Methods

Method

Purpose

Used When

mergeClone

Merges the serialized object and all its privately owned parts (excluding non-private references from it to independent objects) into the working copy clone.

The client edits the object but not its relationships, or marks its independent relationships as transient.

mergeCloneWithReferences

Merges the serialized object and all references into the working copy clone.

The client edits the object and the targets of its relationships and has not marked any attributes as transient.

shallowMergeClone

Merges only serialized object changes to attributes mapped with direct mappings into the working copy clone.

The client edits only the object's direct attributes or has marked all of the object's relationships as transient.

deepMergeClone

Merges the serialized object and everything connected to it (the entire object tree where the serialized object is the root) into the working copy clone.

The client traverses all relationships of the objects and makes changes.

Note: Use deepMergeClone with caution. If two different copies of an object are in the same tree, EclipseLink will merge one set of changes over the other. You should not have any transient attributes in any of your related objects.

Note that if your three-tier client is sufficiently complex, consider using the EclipseLink remote session (see Remote Sessions). It automatically handles merging and lets you use a unit of work on the client.

You can merge clones with both existing and new objects. Because clones do not appear in the cache and may not have a primary key, you can merge new objects only once within a unit of work. If you need to merge a new object more than once, call the unit of work setShouldNewObjectsBeCached method, and ensure that the object has a valid primary key; you can then register the object.

The Merging a Serialized Object example shows one way to update the original object with the changes contained in the corresponding serialized object (rmiClone) received from a client.

Resuming a Unit of Work After Commit

At commit time, a unit of work and its contents expire: you must not use the unit of work nor its clones even if the transaction failed and rolled back.

However, EclipseLink offers the following API that lets you continue working with a unit of work and its clones:

commitAndResume: Commits the unit of work, but does not invalidate it or its clones.

commitAndResumeOnFailure: Commits the unit of work. If the commit transaction succeeds, the unit of work expires. However, if the commit transaction fails, this method does not invalidate the unit of work or its clones. This method lets you modify the registered objects in a failed unit of work and retry the commit transaction.

You should resume a unit of work only in an application that makes repeated changes to the same, small dataset. Reusing the same unit of work while accessing different datasets may result in poor performance.

Reverting a Unit of Work

Under certain circumstances, you may want to abandon some or all changes to clones in a unit of work, but not abandon the unit itself. The following options exist for reverting all or part of the unit of work:

revertObject: Abandons changes to a specific working copy clone in the unit of work

revertAndResume: Uses the backup copy clones to restore all clones to their original states, deregister any new objects, and reinstate any deleted objects.

Using a Nested or Parallel Unit of Work

You can use a unit of work within another unit of work (nesting), or you can use two or more units of work with the same objects in parallel.

How to Use Parallel Unit of Work

To start multiple units of work that operate in parallel, call the acquireUnitOfWork method multiple times on the session. The units of work operate independently of one another and maintain their own cache.

How to Use Nested Unit of Work

To nest units of work, call the acquireUnitOfWork method on the parent unit of work. This creates a child unit of work with its own cache. If a child unit of work commits, it updates the parent unit of work rather than the database. If the parent does not commit, the changes made to the child are not written to the database.

EclipseLink does not update the database or the cache until the outermost unit of work is committed. You must commit or release the child unit of work before you can commit its parent.

Working copy clones from one unit of work are not valid in another units of work: not even between an inner and outer unit of work. You must register objects at all levels of a unit of work where they are used.

Using a Unit of Work with Custom SQL

You can execute native SQL or invoke a stored procedure within a unit of work by using unit of work method executeNonSelectingCall, or by executing a DataModifyQuery. This makes the unit of work begin its database transaction early and execute the call to the data immediately.

If you release the unit of work, it will roll back the database changes. If you commit the unit of work and the commit succeeds, the unit of work will commit the changes to the database.

You can execute a DataModifyQuery only in a unit of work or a database session. You cannot execute a DataModifyQuery in a client or server session directly.

You can execute a DataReadQuery or use session method executeSelectingCall in any session type because these do not modify data.

This example illustrates using SQLCall with the unit of work method executeNonSelectingCall.

Controlling the Order of Delete Operations

The Deleting Objects section explained that EclipseLink always properly arranges (orders) the SQL based on the mappings and foreign keys in your object model and schema. You can control the order of delete operations if you know how to do the following:

How to Use the setShouldPerformDeletesFirst Method of the Unit of Work

By default, EclipseLink does insert and update operations first, before delete operations, to ensure that referential integrity is maintained. This is the preferred approach.

If you are forced to replace an object with unique constraints by deleting it and inserting a replacement, you may cause a constraint violation if the insert operation occurs before the delete operation. In this case, call setShouldPerformDeletesFirst to perform the delete operation before the insert operation.

How to Use the addConstraintDependencies Method of the Descriptor

The constraints used by EclipseLink to determine delete order are inferred from one-to-one and one-to-many mappings. If you do not have such mappings, you can add constraint knowledge to EclipseLink using the descriptor addConstraintDependencies(Class) method.

For example, suppose you have a composition of objects: A contains B (one-to-many, privately owned) and B has a one-to-one, nonprivate relationship with C. You want to delete A (and in doing so the included Bs) but before deleting the Bs, for some of them (not all) you want to delete the associated object C.

How to Use the deleteAllObjects Method Without the addConstraintDependencies Method

In the first option, you do not identify the one-to-many (A to B) relationship as privately owned. When deleting an A object, you must delete all of its B objects, as well as any C objects, as the following example shows:

uow.deleteObject(existingA);
uow.deleteAllObjects(existingA.getBs());
// delete one of the Cs
uow.deleteObject(((B) existingA.getBs().get(1)).getC());

This option produces the following SQL:

DELETE FROM B WHERE (ID = 2)
DELETE FROM B WHERE (ID = 1)
DELETE FROM A WHERE (ID = 1)
DELETE FROM C WHERE (ID = 1)

How to Use the deleteAllObjects Method with the addConstraintDependencies Method

In the second option, keep the one-to-many (A to B) relationship privately owned and add a constraint dependency from A to C, as the following example shows:

session.getDescriptor(A.class).addConstraintDependencies(C.class);

Now the delete code would be as follows:

uow.deleteObject(existingA);
// delete one of the Cs
uow.deleteObject(((B) existingA.getBs().get(1)).getC());

This option produces the following SQL:

DELETE FROM B WHERE (A = 1)
DELETE FROM A WHERE (ID = 1)
DELETE FROM C WHERE (ID = 1)

In both cases, the B object is deleted before A and C. The main difference is that the second option generates fewer SQL statements, as it knows that it is deleting the entire set of Bs related from A.

Using Optimistic Read Locking with the forceUpdateToVersionField Method

You want an OptimisticLockingException thrown at commit time if an object you read in a transaction has changed since it was registered even though you have not changed the object in your transaction (see How to Force a Check of the Optimistic Read Lock).

You modify an object in a transaction in such a way that its version field is not updated but you want to alert other threads of the change by way of the version field (see How to Force a Version Field Update).For example, you change a privately owned object that has its own database table so the parent object's version field is not, by default, updated. In this case, you can use forceUpdateToVersionField to update the parent's version field.As an alternative to this approach, consider Optimistic Version Locking Policies and Cascading.

How to Force a Check of the Optimistic Read Lock

When you read an object with the unit of work, optimistic lock checking is not applied to that object at commit time unless you change the object. However, there are times when you want your transaction to fail if the state of an object has changed since it was read, even though you have not modified the object.

The Optimistic Read Lock with No Version Increment example shows a transaction that updates a mortgage rate by multiplying the central bank prime rate by 1.25. The transaction forces an optimistic read lock on the central prime rate at commit time to ensure that the prime rate has not changed since the transaction began. Note that in this example, the transaction does not increment the version of the unchanged object (the central prime rate).

How to Force a Version Field Update

The unit of work considers an object changed when you modify its direct-to-field or aggregate object mapping attribute. Adding, removing, or modifying objects related to the source object does not render the source object changed for the purposes of the unit of work. In other words, when a relationship is changed in a one-to-many or one-to-one target foreign key mapping, by default, the version field (if any) of the affected object is not changed.

If you configure a descriptor to refresh the cache only if the database version is newer than the cache version (using descriptor method onlyRefreshCacheIfNewerVersion), and such a relationship changes, you will not be able to refresh the object at all. Because the version has not changed, the unit of work method refreshObject and even a query with refreshIdentityMapResults option set to true cannot refresh the object.

Using the unit of work method forceUpdateToVersionField passing in both the unit of work copy clone and true value will ensure that the object's version field is updated when such a change is made. It will also ensure that changes to the object before it is refreshed will result in optimistic locking exceptions, preventing the writing of stale data (see How to Force a Check of the Optimistic Read Lock).

// The following code represents the invoice thread, and calculates a bill for the customer.// Notice that it does not force an update to the version
try {
UnitOfWork uow = session.acquireUnitOfWork();
Customer cloneCustomer = (Customer) uow.registerObject(customer);
Invoice cloneInvoice = (Invoice) uow.registerObject(new Invoice());
cloneInvoice.setCustomer(cloneCustomer);
// calculate service charge
int total = 0;
for(Enumeration enum = cloneCustomer.getServices().elements();
enum.hasMoreElements()) {
total += ((Service) enum.nextElement()).getCost();
}
cloneInvoice.setTotal(total);
// Force optimistic lock checking on the customer to guarantee a valid calculation
uow.forceUpdateToVersionField(cloneCustomer, false);
uow.commit();
}
catch(OptimisticLockException exception) {
// refresh the customer and its privately owned parts
session.refreshObject(cloneCustomer);
// If the customer's services are not privately owned then use// a ReadObjectQuery to refresh all parts
ReadObjectQuery query = new ReadObjectQuery(customer);
// Refresh the cache with the query's result and cascade refreshing // to all parts including customer's services
query.refreshIdentityMapResult();
query.cascadeAllParts();
// refresh from the database
query.dontCheckCache();
session.executeQuery(query);
// retry
}

How to Disable the forceUpdateToVersionField Configuration

The forceUpdateToVersionField configuration that you apply to an object stays in effect for the lifetime of your unit of work. After you commit your transaction, forceUpdateToVersionField configuration no longer applies.

To remove forceUpdateToVersionField configuration from an object before commit time, use the unit of work method removeForceUpdateToVersionField. EclipseLink will not apply optimistic read locking to the object unless you change it in this transaction (that is, unless you modify its direct-to-field or aggregate object mapping attribute).

Implementing User and Date Auditing with the Unit of Work

Auditing data source changes is a common requirement: when a user commits a change to the data source, the application updates a field in the data source to record the user who made the change and the date.

For example, suppose each row in your database schema includes fields lastUpdateBy (to record the user name of the user who commits a change) and lastUpdateOn (to record the date of the change).

You can use UnitOfWork method setProperty to record the name of the user who acquires the UnitOfWork and implement a descriptor event listener for AboutToUpdateEvent descriptor events that extracts the property and updates the lastUpdateBy and lastUpdateOn fields.

Integrating the Unit of Work with an External Transaction Service

To support transactions managed by an application server's external transaction service, EclipseLink supports external connection pools and external transaction controller classes for supported servers. This lets you incorporate external transaction service support into your application, and use the unit of work with transactions managed externally by the server. For more information, see Unit of Work Transaction Demarcation.
To integrate a unit of work with an external transaction service, you must enable the use of the following:

After you configure external connection pool and external transaction controller support, you use a unit of work in your EclipseLink application as you would typically, with few exceptions. This section describes these exceptions as follows:

How to Acquire a Unit of Work with an External Transaction Service

You use a unit of work to commit changes to a data source even when using an external transaction service. To ensure that only one unit of work is associated with a given transaction, use the getActiveUnitOfWork method to acquire a unit of work, as shown in the Using a Unit of Work with an External Transaction Service example.

Note: Although there are other ways to commit changes to a data source using an external transaction service, we recommend using the getActiveUnitOfWork method.

The getActiveUnitOfWork method searches for an existing external transaction in the following way:

If there is an active external transaction and a unit of work is already associated with it, return this unit of work.

If there is an active external transaction with no associated unit of work, then acquire a new unit of work, associate it with the transaction, and return it.

How to Use a Unit of Work when an External Transaction Exists

When getActiveUnitOfWork returns a unit of work that is not null, you are associated with an existing external transaction. Use the unit of work as usual.

As the external transaction was not started by the unit of work, issuing a commit on it will not cause the external transaction to be committed. The unit of work will defer to the application or container that began the transaction. When the external transaction does get committed by the container, EclipseLink receives synchronization callbacks at key points during the commit transaction.

The unit of work sends the required SQL to the database when it receives the beforeCompletion callback.

The unit of work uses the Boolean argument received from the afterCompletion callback to determine if the commit was successful (true) or not (false).

If the commit transaction was successful, the unit of work merges changes to the session cache. If the commit transaction was unsuccessful, the unit of work discards the changes.

How to Use the Unit of Work to Handle External Transaction Timeouts and Exceptions

Handling External Transaction Commit Timeouts

When an external transaction is committed, the external transaction service expects each transaction owner to commit its portion of the overall transaction within a finite amount of time. If any individual transaction exceeds this timeout interval, the external transaction service will fail the specific transaction and roll it back (or mark it rollback only).

If your transaction is large and its commit transaction may exceed the external transaction service timeout interval, use UnitOfWork method writeChanges to write changes to the data source before committing the external transaction. This will reduce the time it takes for your part of the global transaction to commit.

Handling External Transaction Commit Exceptions

When you use the unit of work with an external transaction service, commit exceptions may not be thrown until long after your application thread calls its UnitOfWork method commit and returns. In this case, commit exceptions are thrown to the client of the container-managed transaction (CMT) call, forcing the client to handle this server-side failure.

You can use the UnitOfWork method writeChanges to write changes to the data source before the external transaction commits. This allows your application thread to catch and handle most exceptions that could be thrown at the time the external transaction service commits the global transaction.

Database Transaction Isolation Levels

Achieving a particular database transaction isolation level in an EclipseLink application is more involved than simply using the DatabaseLogin method setTransactionIsolation.

In a typical EclipseLink application and in Java EE applications that require persistence in general, a variety of factors affect when database transaction isolation levels apply and to what extent a particular database transaction isolation can be achieved.

This section describes these factors and provides guidelines on configuring and using EclipseLink to achieve each database transaction isolation level to the extent possible given these factors.

What You May Need to Know About General Factors Affecting Transaction Isolation Level

This section describes some of the important factors and variables that may affect the degree to which your EclipseLink application can achieve a particular database transaction isolation level. These factors include the following:

If the external application can update a version field in the database, your EclipseLink application could use alwaysRefreshCache in conjunction with Descriptor method onlyRefreshCacheIfNewerVersion to ensure that refresh operations are performed only when required. Another, recommended way to achieve this, is to use the descriptor isolated cache option (see Cache Isolation), as well as cache invalidation (see Cache Invalidation).

EclipseLink Coordinated Cache

Consider multiple EclipseLink applications (each running on its own application server instance) configured to use a distributed, coordinated cache (as described in Cache Coordination). An EclipseLink application instance first commits changes to its own cache before the change is distributed to other caches. Because cache coordination is not instantaneous, there is a possibility that one EclipseLink application instance may read an older version of an object from its cache before a cache coordination message is received.

This method sets the transaction isolation level used for both database read and write operations on the database connections obtained from either an internal or external connection pool), for both internal and external transactions.

However, with EclipseLink, by default read operations use a different database connection than write operations, typically obtained from an external connection pool, or may use the cache, bypassing the database entirely. Thus, with EclipseLink, by default, read operations are always performed outside the transaction or unit of work, even if you perform the read operation within a transaction or unit of work. Although database transaction isolation applies to both read and write connections, the read is not performed as part of the transaction. Therefore, the read operation overrides the transaction isolation set on the database.

Depending on the level of transaction isolation you are trying to achieve, you may require that the same transaction isolation be applied to both read and write operations. You must take special action to make EclipseLink use the same connection for both read and write operations. For more information, see Reading Through the Write Connection.

Reading Through the Write Connection

Recall that EclipseLink, by default, performs read operations with a different database connection than used for write operations (see DatabaseLogin Method setTransactionIsolation). However, from the perspective of database transaction isolation, there is a one-to-one relationship between transaction and database connection: that is, all database operations (including read operations) must use the same database connection in order to achieve a particular database transaction isolation level.

In general, when EclipseLink performs a read operation, if a write connection already exists, EclipseLink will use the write connection for the read operation. This is called "reading through the write connection." If a write connection does not yet exist, EclipseLink will acquire another connection and use that for the read operation.

You can configure EclipseLink to allocate a write connection early using any of the following:

Caution: Depending on the database transaction isolated level reading through the write connection may lock the object being read. This will affect performance and reduce concurrency. We recommend that you do not use these advanced techniques unless strict database transaction isolation is absolutely necessary.

Pessimistic Locking Query

When you use pessimistic locking (ObjectLevelReadQuery methods acquireLocks or acquireLocksWithoutWaiting or Session method refreshAndLockObject), EclipseLink does the following:

Allocates a write connection used for both read and write operations.

Always reads from the database.

Always updates the cache with the database version.

Unit of Work Method beginTransactionEarly

This method is advanced API. If you call beginTransactionEarly on an instance of a unit of work, all read operations should be performed through that instance of the unit of work.

This method starts a database transaction immediately: any objects you read will lock data in the database before commit time, reducing concurrency.

If you are using isolated client sessions, you can use exclusive connections for reading isolated data. In this case, you can configure EclipseLink to acquire an exclusive connection from the write connection pool and use it for both writing and reading isolated data. However, EclipseLink still acquires a shared connection from the read connection pool for reading nonisolated data.

Managing Cache Access

By default, EclipseLink uses the shared session cache as much as possible. Doing so increases concurrency and improves performance. However, to achieve a particular transaction isolation level, you may need to avoid the cache using some or all the following:

Isolated Client Session Cache

This method always goes to the database for the initial read operation of an object whose descriptor is configured as isolated. By avoiding the shared session cache, you do not need to use the more complicated descriptor and query APIs to disable cache hits or always refresh. For more information about isolated client sessions, see Isolated Client Sessions. This is particularly useful for achieving serializable transaction isolation (see What You May Need to Know About Serializable Read Levels)

ReadObjectQuery

This API goes to the database unless it is a primary key-based query, in which case it will go to the cache first. For information on how to avoid the cache entirely in this case, see Descriptor Method disableCacheHits.

ReadAllQuery

This API always goes to the databases. For information on how to avoid the cache entirely in this case, see the description of the Descriptor method alwaysRefreshCache in Cache Refresh API.

Descriptor Method disableCacheHits

This API allows for cache hits on primary key, read-object queries to be disabled. This can be used with the Descriptor method alwaysRefreshCache to ensure queries always go to the database.

DatabaseQuery Method dontMaintainCache

This is a query-level means of preventing objects from being added to the shared session cache. Using an isolated client session (see Isolated Client Session Cache) is a simpler approach to achieving the same ends.

What You May Need to Know About Read Uncommitted Level

We do not recommend using this transaction isolation level.

In general, a read uncommitted operation is not necessary. Using EclipseLink, a transaction isolation of read committed gives you better performance than read uncommitted but with greatly improved data integrity.

What You May Need to Know About Read Committed Level

Using the unit of work guarantees that you will read only committed data in the shared session cache or committed data in the database.

What You May Need to Know About Repeatable Read Levels

To achieve repeatable read operations, you must use a unit of work, you must register all objects in the unit of work (both objects you intend to modify and objects you intend only to read), and you must use ObjectLevelReadQuery method conformResultsInUnitOfWork or Descriptor method alwaysConformResultsInUnitOfWork.

By doing so, each time you query a registered object, you will get the version of the object as it currently is in your unit of work.

If you are only concerned about the write aspect of serializable, optimistic locking is sufficient.

To prevent phantom read transactions (that is, when a transaction detects that new records that have been added to the database after the transaction started), use the ReadQuery method cacheQueryResults.

Troubleshooting a Unit of Work

This section examines common unit of work problems and debugging techniques, and describes the following:

How to Avoid the Use of Post-Commit Clones

A common unit of work error is holding on to clones after commit time. Typically the clones are stored in a static variable and you incorrectly believe that this object is the cache copy. This leads to problems when another unit of work makes changes to the object and what you believe is the cache copy is not updated (because a unit of work updates only the cache copy, not old clones).

Consider the error in the Incorrect Use of Handle to Clone example. In this example you get a handle to the cache copy of a Pet and store it in the static CACHE_PET. We get a handle to a working copy clone and store it in the static CLONE_PET. In a future unit of work, the Pet is changed.

If you incorrectly store global references to clones from units of work, you often expect them to be updated when the cache object is changed in a future unit of work. Only the cache copy is updated.

Incorrect Use of Handle to Clone

// Read a Pet from the database, store in static
CACHE_PET = (Pet)session.readObject(Pet.class);
// Put a clone in a static. This is a bad idea and is a common error
UnitOfWork uow = session.acquireUnitOfWork();
CLONE_PET = (Pet)uow.readObject(Pet.class);
CLONE_PET.setName("Hairy");
uow.commit();
// Later, the pet is changed again
UnitOfWork anotherUow = session.acquireUnitOfWork();
Pet petClone = (Pet)anotherUow.registerObject(CACHE_PET);
petClone.setName("Fuzzy");
anotherUow.commit();
// If you incorrectly stored the clone in a static and thought it should be // updated when it is later changed, you would be wrong: only the cache copy is // updated; NOT OLD CLONES
System.out.println("CACHE_PET is" + CACHE_PET);
System.out.println("CLONE_PET is" + CLONE_PET);

How to Determine Whether or Not an Object Is the Cache Object

In Modifying an Object section, it was noted that it is possible to read any particular instance of a class by executing:

session.readObject(Class);

There is also a readObject method that takes an object as an argument: this method is equivalent to doing a ReadObjectQuery on the primary key of the object passed in. For example, the following code is equivalent to the code in the subsequent example:

Also note that primary key-based queries, by default, will return what is in the cache without going to the database. As a result, you can use very quick and simple method for accessing the cache copy of an object, as shown in this example:

Is CACHE_PET the Cache copy of the object: true
Is CLONE_PET the Cache copy of the object: false

How to Dump the Contents of a Unit of Work

The unit of work has several debugging methods to help you analyze performance or track down problems with your code. The most useful is printRegisteredObjects, which prints all the information about known objects in the unit of work. Use this method to see how many objects are registered and to make sure objects you are working on are registered.

To use this method, you must have log messages enabled for the session that the unit of work is from. Session log messages are disabled by default. To enable log messages for a specific level, use the session setLogLevel method, as shown in this example:

How to Handle Exceptions

Handling Exceptions at Commit Time

EclipseLink exceptions are instances of RuntimeException, which means that methods that throw them do not have to be placed in a try-catch block.

However, the unit of work commit method is one that should be called within a try-catch block to deal with problems that may arise.

This example shows one way to handle unit of work exceptions:

Handling Unit of Work Commit Exceptions

UnitOfWork uow = session.acquireUnitOfWork();
Pet petClone = (Pet)uow.registerObject(newPet);
petClone.setName("Assume this name is too long for a database constraint");
// Assume that the name argument violates a length constraint on the database.// This will cause a DatabaseException on commit
try {
uow.commit();
}
catch (EclipseLinkException tle) {
System.out.println("There was an exception: " + tle);
}

This code produces the following output:

There was an exception: EXCEPTION [ECLIPSELINK-6004]:
org.eclipse.persistence.exceptions.DatabaseException

If you use optimistic locking, you must catch exceptions at commit time because the exception raised is the indication that there was an optimistic locking problem. Optimistic locking allows all users to access a given object, even if it is currently in use in a transaction or unit of work. When the unit of work attempts to change the object, the database checks to ensure that the object has not changed since it was initially read by the unit of work. If the object has changed, the database raises an exception, and the unit of work rolls back the transaction. For more information, see Locking and the Unit of Work.

If you are using an external transaction service, exceptions may be thrown long after your UnitOfWork code has returned. Using UnitOfWork method writeChanges, you can catch and handle most exceptions before the external transaction is committed. For more information, see Handling External Transaction Commit Exceptions.

Handling Exceptions During Conforming

You can conform query results in a unit of work across one-to-many relationships and a combination of both one-to-one and one-to-many relationships. This example illustrates a query across two levels of relationships, one-to-many and one-to-one.

By default, any exceptions thrown during conforming are suppressed. However, you can use the UnitOfWork method setShouldThrowConformExceptions to make the unit of work throw all conforming exceptions. This method takes one int argument with the following values:

How to Validate a Unit of Work

The unit of work validates object references at commit time. If an object registered in a unit of work references other unregistered objects, this violates object transaction isolation, and causes EclipseLink validation to raise an exception.

Although referencing unregistered objects from a registered object can corrupt the session cache, there are applications in which you want to disable validation. EclipseLink offers the following APIs to toggle validation:

dontPerformValidation: disables validation

performFullValidation: enables validation

Validating the Unit of Work Before Commit Time

If the unit of work detects an error when merging changes into the session cache, it throws a QueryException. Although this exception specifies the invalid object and the reason it is invalid, it may still be difficult to determine the cause of the problem.

In this case, you can use the validateObjectSpace method to test registered objects and provide the full stack trace of all traversed objects. This may help you more easily find the problem. You can call this method at any time on a unit of work.