Skip to main content

Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Using Advanced Unit of Work API (ELUG)

Elug draft icon.png For the latest EclipseLink documentation, please see http://www.eclipse.org/eclipselink/documentation/


Contents

Related Topics

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.

Creating and Registering an Object in One Step

UnitOfWork uow = session.acquireUnitOfWork();
Pet petClone = (Pet)uow.newInstance(Pet.class);
petClone.setId(100);
petClone.setName("Fluffy");
petClone.setType("Cat");
uow.commit();


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.


Using registerAllObjects

UnitOfWork uow = session.acquireUnitOfWork();
Collection toRegister = new ArrayList(2);
VetVisit vv1 = new VetVisit();
vv1.setId(70);
vv1.setNotes("May have flu");
vv1.setSymptoms("High temperature");
toRegister.add(vv1);

VetVisit vv2 = new VetVisit();
vv2.setId(71);
vv2.setNotes("May have flu");
vv2.setSymptoms("Sick to stomach");
toRegister.add(vv2);

uow.registerAllObjects(toRegister);
uow.commit();

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.

For more information, see Creating Relational Aggregate Descriptors.


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).


What You May Need to Know About Object Registration

This table summarizes the UnitOfWork object registration methods.

UnitOfWork Object Registration API

Task UnitOfWork Method Result

Create new object without children

registerObject

New object is reference to cache object after commit.

Create new object without children

registerNewObject

New object is reference to cache object after commit.

Performance enhancement: no clone created.

Create new child object for existing parent: unidirectional relationship

Parent: registerObjectChild: no registration necessary

EclipseLink cascades registration to all new child objects reachable from the registered object: no need to register new child objects.

Create new child object for existing parent: unidirectional relationship

Parent: registerObjectChild: registerObject

New object is reference to cache object after commit.

Create new child object for existing parent: bidirectional relationship

Parent: registerObjectChild: no registration necessary

EclipseLink cascades registration to all new child objects reachable from the registered object: no need to register new child objects.

Create new child object for existing parent: bidirectional relationship

Parent: registerObjectChild: registerNewObject

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

For more information, see the following:

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.

Alternatively, you can configure an object-level ready query as a read-only query.


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.

For more information, see Configuring Read-Only Descriptors.


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.

Before you use conforming, be aware of its limitations (see How to Use Conforming) and make sure that conforming is actually necessary. For example, consider the alternative described in What you may need to Know About Conforming Query Alternatives.


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

Conforming is supported by the following queries:

  • ReadObjectQuery
  • ReadAllQuery
  • Expressions
  • EJB QL
  • Query by example
  • 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.

Consider Conforming Example. In this example, you have Employee objects with an address attribute configured for indirection mapped by a one-to-one mapping to an Address object.


Conforming Example

Conforming Example

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:

  1. Using the UnitOfWork, read in all employees.All these Employee objects are now registered with the UnitOfWork
  2. Using the same UnitOfWork, access the employees' addresses, instantiating the indirect relationships.
  3. Modify the employees' addresses, changing some of the addresses to be in Ottawa.
  4. 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.
  5. 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:

  1. Using the UnitOfWork, read in all addresses outside of Ottawa.
  2. Modify the addresses, changing some of the addresses to be in Ottawa
  3. Commit the transaction.
  4. 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.


Using Conforming Queries

UnitOfWork uow = session.acquireUnitOfWork();
Pet pet2 = new Pet();
Pet petClone = (Pet)uow.registerObject(pet2);
petClone.setId(200);
petClone.setType("Cat");
petClone.setName("Mouser");

ReadAllQuery readAllCats = new ReadAllQuery();
readAllCats.setReferenceClass(Pet.class);
ExpressionBuilder builder = new ExpressionBuilder();
Expression catExp = builder.get("type").equal("Cat");
readAllCats.setSelectionCriteria(catExp);

List allCats = (List)uow.executeQuery(readAllCats);

System.out.println("All 'Cats' read through UOW are: " + allCats); 
uow.commit();

This produces the following output:


All 'Cats' read through UOW are: [Pet type Cat named Fluffy id:100]

If you tell the query readAllCats to include new objects:


readAllCats.conformResultsInUnitOfWork();

The output would be as follows:


All 'Cats' read through UOW are: [Pet type Cat named Fluffy id:100, Pet type Cat named Mouser id:200]

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.

To configure a descriptor to always conform in the unit of work using EclipseLink, see Configuring Unit of Work Conforming at the Descriptor Level.


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 Method writeChanges

UnitOfWork uow = session.acquireUnitOfWork();
Pet pet = new Pet();
Pet petClone = (Pet)uow.registerObject(pet);
petClone.setId(100);
petClone.setName("Fluffy");
petClone.setType("Cat");

uow.writeChanges();

// Use uow to perform report, noncaching, and data read and modify queries
// against the changes made so far

uow.commit();

However, you can call writeChanges only once; any attempt to register objects or to execute object-level queries will throw an exception.

For more information, see Writing Changes Before Commit Time


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:


UnitOfWork uow = session.acquireUnitOfWork();

In the module that creates the new employee, note the following:


Pet newPet = new Pet();
Pet newPetClone = (Pet)uow.registerObject(newPet);
uow.setProperty("NEW PET", newPet);

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.


Merging a Serialized Object

update(Object original, Object rmiClone) {
    original = uow.registerObject(original);
    uow.mergeCloneWithRefereneces(rmiClone);
    uow.commit();
}

For more information, see Indirection, Serialization, and Detachment.


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.

This example shows how to use the commitAndResume method.


Using the commitAndResume Method


UnitOfWork uow = session.acquireUnitOfWork();
PetOwner petOwnerClone = (PetOwner)uow.readObject(PetOwner.class);
petOwnerClone.setName("Mrs. Newowner");
uow.commitAndResume();
petOwnerClone.setPhoneNumber("KL5-7721");
uow.commit();

The commitAndResume call produces the following SQL:


UPDATE PETOWNER SET NAME = 'Mrs. Newowner' WHERE (ID = 400)

Then, the commit call produces the following SQL:


UPDATE PETOWNER SET PHN_NBR = 'KL5-7721' WHERE (ID = 400)


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.

This section describes the following:


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.

This example shows how to use nested units of work.


Using Nested Units of Work

UnitOfWork outerUOW = session.acquireUnitOfWork();
Pet outerPetClone = (Pet)outerUOW.readObject(Pet.class);

UnitOfWork innerUOWa = outerUOW.acquireUnitOfWork();
Pet innerPetCloneA = (Pet)innerUOWa.registerObject(outerPetClone);
innerPetCloneA.setName("Muffy");
innerUOWa.commit();

UnitOfWork innerUOWb = outerUOW.acquireUnitOfWork();
Pet innerPetCloneB = (Pet)innerUOWb.registerObject(outerPetClone);
innerPetCloneB.setName("Duffy");
innerUOWb.commit();
outerUOW.commit();


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.


Using the executeNonSelectingCall Method

uow.executeNonSelectingCall(new SQLCall(mySqlString));


WARNING: Allowing an unverified SQL string to be passed into methods makes your application vulnerable to SQL injection attacks.


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.

There are the following possible solutions:

  1. You can use the deleteAllObjects method without the addConstraintDependencies method (see How to Use the deleteAllObjects Method Without the addConstraintDependencies Method)
  2. You can use the deleteAllObjects method with the addConstraintDependencies method (see How to Use the deleteAllObjects Method With the addConstraintDependencies Method)

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

If your descriptors are configured to use an optimistic version locking policy (see Optimistic Version Locking Policies) or field locking policy (see Optimistic Field Locking Policies), use the unit of work method forceUpdateToVersionField to solve either or both of the following problems:

  • 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.

To remove forceUpdateToVersionField configuration from an object before a commit operation, use the unit of work method removeForceUpdateToVersionField (see How to Disable the forceUpdateToVersionField Configuration).


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).


Optimistic Read Lock with No Version Increment

try {
    UnitOfWork uow = session.acquireUnitOfWork();
    MortgageRate cloneMortgageRate = (MortgageRate)uow.registerObject(mortgageRate);
                     
    CentralPrimeRate cloneCentralPrimeRate = (CentralPrimeRate)uow.registerObject(CentralPrimeRate);
                      
    // change the Mortgage Rate 
    cloneMortgageRate.setRate(cloneCentralPrimeRate.getRate() * 1.25);
    // optimistic read lock check on Central prime rate with no version update
    uow.forceUpdateToVersionField(cloneCentralPrimeRate, false);
    uow.commit();
} 
catch(OptimisticLockException exception) {
    // refresh the out-of-date object
    session.refreshObject(exception.getObject());
    // Retry

}

For another example that forces both optimistic locking and a version field update, see Optimistic Read Lock with Version Increment: Service Thread example in How to Force a Version Field Update.


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).

Optimistic Read Lock with Version Increment: Service Thread and Optimistic Read Lock with Version Increment: Invoice Thread examples show transactions executing in separate threads that access the same customer object concurrently. The unit of work method forceUpdateToVersionField is used to ensure that changes to the customer object in one thread are detected by the other threads.

The Optimistic Read Lock with Version Increment: Service Thread example shows a transaction in which an invoice thread calculates an invoice for a customer. The Optimistic Read Lock with Version Increment: Invoice Thread example shows a transaction in which another thread, the service thread, adds a service to the same customer or modifies the current service. In either case, the service thread must inform the invoice thread, which adds the changes to the invoice.


Optimistic Read Lock with Version Increment: Service Thread

// The following code represents the service thread. Notice that the thread forces a version update
try {
    UnitOfWork uow = session.acquireUnitOfWork();
    Customer cloneCustomer = (Customer uow.registerObject(customer);
    Service cloneService = (Service uow.registerObject(service);
    // add a service to customer 
    cloneService.setCustomer(cloneCustomer);
    cloneCustomer.getServices().add(cloneService);
    // Modify the customer version to inform other application that the customer has changed 
    uow.forceUpdateToVersionField(cloneCustomer, true);
    uow.commit();
}
catch (OptimisticLockException exception) {
    // refresh out-of-date object

    session.refreshObject(exception.getObject());
    // retry
}


Optimistic Read Lock with Version Increment: Invoice Thread

// 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.

For more information, see the following:


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.
  • If there is no active external transaction in progress, return null.

If EclipseLink returns a unit of work that is not null, use it exactly as you would typically: the only exception is that you do not call the commit method (see How to Use a Unit of Work when an External Transaction Exists).

If EclipseLink returns a null unit of work, start an external transaction explicitly through the UserTransaction interface.


Using a Unit of Work with an External Transaction Service

// Read in any pet
Pet pet = (Pet)clientSession.readObject(Pet.class);
UnitOfWork uow = clientSession.getActiveUnitOfWork();
if (uow == null) {
    throw new RuntimeException("No transaction started");; 
}
Pet petClone = (Pet)uow.registerObject(pet);
petClone.setName("Furry");


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.

The Unit of Work when an External Transaction Exists figure shows the life cycle of a unit of work when an external transaction exists.


Unit of Work when an External Transaction Exists

Unit of Work when an External Transaction Exists


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

When the getActiveUnitOfWork method returns a null unit of work, there is no existing external transaction. You must start a new external transaction.

Do this either by starting an external transaction explicitly using the UserTransaction interface, or by acquiring a new unit of work using the acquireUnitOfWork method on the server session.

Use the unit of work as usual.

Once the modifications to registered objects are complete, you must commit the transaction either explicitly through the UserTransaction interface or by calling the unit of work commit method.

The transaction synchronization callbacks are then invoked on, and the database updates and cache merge occur based upon those callbacks.

Unit of Work when No External Transaction Exists

Unit of Work when No External Transaction Exists


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

This section describes the following two common problems with external transactions, and the ways to handle them:


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.

For more information about the UnitOfWork method writeChanges, including restrictions and warnings, see Writing Changes Before Commit Time.


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.

For more information about the UnitOfWork method writeChanges, including restrictions and warnings, see Writing Changes Before Commit Time.

For more information on handling unit of work exceptions in general, see How to Handle Exceptions.


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.

This section describes the following:


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:


External Applications

In many cases, your EclipseLink application is not the only application that can update to the database. External, non-EclipseLink applications, can also update the database at any time.

In this case, your EclipseLink application must use the ObjectLevelReadQuery method refreshIdentityMapResult (see How to Refresh the Cache) or Descriptor methods alwaysRefreshCache and disableCacheHits (see Configuring Cache Refreshing).

For more information, see Cache Access.

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.

To provide your EclipseLink application with the most up-to-date version of an object use the descriptor isolated cache option, as well as Cache Invalidation.

You can also avoid stale data by using Descriptor methods alwaysRefreshCache and disableCacheHits. For more information on the disableCacheHits method, see Managing Cache Access.


Caution: Using Descriptor methods alwaysRefreshCache and disableCacheHits will result in frequent database hits. Use only when absolutely necessary.


DatabaseLogin Method setTransactionIsolation

Use the DatabaseLogin method setTransActionIsolation to configure the database transaction isolation level that EclipseLink applies to any database connection it obtains, for example:

databaseLogin.setTransactionIsolation(DatabaseLogin.TRANSACTION_SERIALIZABLE);

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.


ConnectionPolicy Method setShouldUseExclusiveConnection

Client sessions can access the data source using a connection pool or an exclusive connection. To use an exclusive connection, acquire your client session using a ConnectionPolicy (see How to Acquire a Client Session that Uses Exclusive Connections).

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.

For more information, see Exclusive Write Connections.


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.


What You May Need to Know About Serializable Read Levels

To achieve serializable transaction isolation with EclipseLink, we recommend that you use an isolated client session as follows:

  1. Configure the database transaction isolation as serializable.
  2. Configure objects as isolated (see Configuring Cache Isolation at the Project Level or Configuring Cache Isolation at the Descriptor Level).
  3. Use the UnitOfWork method beginTransactionEarly (see Unit of Work Method beginTransactionEarly).

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);

The two System.out calls produce the following output:

CACHE_PET isPet type Cat named Fuzzy id:100
CLONE_PET isPet type Cat named Hairy id:100


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:

session.readObject(pet);

The following is equivalent to the preceding code:

ReadObjectQuery query = new ReadObjectQuery();
query.setReferenceClass(Pet.class);
ExpressionBuilder builder = new ExpressionBuilder();
Expression exp = builder.get("id").equal(pet.getId());
query.setSelectionCriteria(exp);
session.executeQuery(query);

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:


Testing If an Object Is the Cache Object

//Here is a test to see if an object is the cache copy

boolean cached = CACHE_PET == session.readObject(CACHE_PET);
boolean cloned = CLONE_PET == session.readObject(CLONE_PET);
System.out.println("Is CACHE_PET the Cache copy of the object: " + cached);
System.out.println("Is CLONE_PET the Cache copy of the object: " + cloned);

This code produces the following output:

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:


Dumping the Contents of a Unit of Work

int level = session.getLogLevel();
session.setLogLevel(SessionLog.FINE); // Enable log messages and set logging level

UnitOfWork uow = session.acquireUnitOfWork();
Pet petClone = (Pet)uow.readObject(Pet.class);
petClone.setName("Mop Top");

Pet pet2 = new Pet();
pet2.setId(200);
pet2.setName("Sparky");
pet2.setType("Dog");
uow.registerObject(pet2);

uow.printRegisteredObjects(); 
uow.commit();
session.setLogLevel(level); // Reset logging to default level

This example produces the following output:

UnitOfWork identity hashcode: 32373
Deleted Objects:

All Registered Clones:
    Key: [100] Identity Hash Code:13901  Object: Pet type Cat named Mop Top id:100
    Key: [200] Identity Hash Code:16010  Object: Pet type Dog named Sparky id:200

New Objects:
    Key: [200] Identity Hash Code:16010  Object: Pet type Dog named Sparky id:200

How to Handle Exceptions

You can handle exceptions at commit time or during conforming.


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.


Querying Across Two Levels of Relationship

Expression exp = 
  bldr.anyOf("managedEmployees").get("address").get("city").equal("Perth");

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:

  • 0–do not throw conform exceptions (default)
  • 1–throw all conform exceptions

For more information on customizing exception handling when using conforming and in-memory queries, see Handling Exceptions Resulting from In-Memory Queries.


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.

Back to the top