Skip Headers
Oracle® Application Server TopLink Application Developer's Guide
10g Release 2 (10.1.2)
Part No. B15901-01
  Go To Documentation Library
Home
Go To Product List
Solution Area
Go To Table Of Contents
Contents
Go To Index
Index

Previous
Previous
Next
Next
 

Advanced Unit of Work

This section explores more advanced Unit of Work API calls and techniques most commonly used later in the development cycle, including:

For more information about integrating the Unit of Work with J2EE and external transaction controllers, see "J2EE Integration".

For more information about the available methods for the UnitOfWork, see the Oracle Application Server TopLink API Reference.

Troubleshooting a Unit of Work

This section examines common Unit of Work problems and debugging techniques, including:

Avoiding the Use of Postcommit Clones

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

Consider the error in Example 7-15. In this example you get a handle to the cache copy of a Pet and store it in the static CACHE_PET. You get a handle to a working copy and store it in the static CLONE_PET. In a future Unit of Work, the Pet is changed.

Developers who incorrectly store global references to clones from Units of Work often expect them to be updated when the cache object is changed in a future Unit of Work. Only the cache copy is updated.

Example 7-15 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's 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

Determining Whether an Object Is the Cache Object

"Modifying an Object" 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 performing a ReadObjectQuery on the primary key of the object passed in. For example, the following:

session.readObject(pet);

Is equivalent to the following:

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, return what is in the cache without going to the database.

Given this, there is a quick and simple method for accessing the cache copy of an object as shown in Example 7-16.

Example 7-16 Testing Whether 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

Dumping 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 objects known 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, use the session logMessages method. To disable log messages, use the session dontLogMessages method, as shown in Example 7-17.

Example 7-17 Dumping the Contents of a Unit of Work

session.logMessages(); // enable log messages
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.dontLogMessages(); // disable log messages

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

Handling Exceptions

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

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

Example 7-18 shows one way to handle Unit of Work exceptions:

Example 7-18 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 (TopLinkException tle) {
    System.out.println("There was an exception: " + tle);
}

This code produces the following output:

There was an exception: EXCEPTION [ORACLEAS TOPLINK-6004]: oracle.toplink.exceptions.DatabaseException

Catching exceptions at commit time is mandatory if you are using optimistic locking 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 Policy".

Creating and Registering an Object in One Step

This example illustrates 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 pattern to create your objects (and specified this in the builder), the newInstance method will use the appropriate factory.

Example 7-19 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();

Using registerNewObject

This example examines how to use the registerNewObject method, including:

Registering a New Object with registerNewObject

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 registerNewObject in situations where:

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

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

Example 7-20 shows how to register a new object with the registerNewObject method:

Example 7-20 Registering a New Object with the registerNewObject Method

UnitOfWork uow = session.acquireUnitOfWork();
    PetOwner existingPetOwnerClone =
        PetOwner)uow.readObject(PetOwner.class);

    Pet newPet = new Pet();
    newPet.setId(900);
    newPet.setType("Lizzard");
    newPet.setName("Larry");
    newPet.setPetOwner(existingPetOwnerClone);

    uow.registerNewObject(newPet); 
uow.commit();

When you use registerNewObject, do not use the variable newPet after the Unit of Work is committed. The new object is the clone, and if you need the cache version of the object, you must query for it. If you needed a handle to the cache version of the Pet after the Unit of Work has committed, then use the first approach described in "Associations: New Source to Existing Target Object". In that example, the variable newPet is the cache version after the Unit of Work is committed.

Associating New Objects with One Another

At commit time, OracleAS TopLink can determine whether an object is new. In "Associations: New Target to Existing Source Object", it shows that if a new object is reachable from a clone, you do not need to register it. OracleAS TopLink effectively performs a registerNewObject to all new objects it can reach from registered objects.

When working with new objects, remember the following rules:

  • Only reachable or registered objects will be persisted.

  • 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 result is the clone—and the argument is considered the cache version.

Example 7-21 shows how to associate new objects with the registerNewObject method.

Example 7-21 Associating New Objects with the registerNewObject Method

UnitOfWork uow = session.acquireUnitOfWork();
    Pet newPet = new Pet();
    newPet.setId(150);
    newPet.setType("Horse");
    newPet.setName("Ed");

    PetOwner newPetOwner = new PetOwner();
    newPetOwner.setId(250);
    newPetOwner.setName("George");
    newPetOwner.setPhoneNumber("555-9999");

    VetVisit newVetVisit = new VetVisit();
    newVetVisit.setId(350);
    newVetVisit.setNotes("Talks a lot");
    newVetVisit.setSymptoms("Sore throat");

    newPet.getVetVisits().addElement(newVetVisit);
    newVetVisit.setPet(newPet);
    newPet.setPetOwner(newPetOwner);

    uow.registerNewObject(newPet);
uow.commit();

However, after the Unit of Work, do not use the variables newPet, newPetOwner, and newVetVisit as they are technically copies from the Unit of Work.

If you need a handle to the cache version of these business objects, you can query for them, or you can perform the Unit of Work as shown in Example 7-22.

Example 7-22 Associating New Objects with the registerObject Method and Retaining a Handle to the Cache Objects

UnitOfWork uow = session.acquireUnitOfWork();
    Pet newPet = new Pet();
    Pet newPetClone = (Pet)uow.registerObject(newPet);
    newPetClone.setId(150);
    newPetClone.setType("Horse");
    newPetClone.setName("Ed");

    PetOwner newPetOwner = new PetOwner();
    PetOwner newPetOwnerClone = 
        (PetOwner)uow.registerObject(newPetOwner);
    newPetOwnerClone.setId(250);
    newPetOwnerClone.setName("George");
    newPetOwnerClone.setPhoneNumber("555-9999");

    VetVisit newVetVisit = new VetVisit();
    VetVisit newVetVisitClone = 
        (VetVisit)uow.registerObject(newVetVisit);
    newVetVisitClone.setId(350);
    newVetVisitClone.setNotes("Talks a lot");
    newVetVisitClone.setSymptoms("Sore throat");

    newPetClone.getVetVisits().addElement(newVetVisitClone);
    newVetVisitClone.setPet(newPetClone);
    newPetClone.setPetOwner(newPetOwnerClone);
uow.commit(); 

Using registerAllObjects

The registerAllObjects method takes a Collection of objects as an argument and returns a Collection of clones, thereby allowing you to register many objects at once, as shown in Example 7-23.

Example 7-23 Using registerAllObjects

UnitOfWork uow = session.acquireUnitOfWork();
    Collection toRegister = new Vector(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();

Using Registration and Existence Checking

When OracleAS TopLink writes an object to the database, OracleAS TopLink runs an existence check to determine whether to perform an insert or an update. You can specify the default existence checking policy for a project as a whole or on a perdescriptor basis. By default, OracleAS TopLink uses the check cache existence checking policy.

This section explains how to use one of the following existence checking policies to accelerate object registration:

Check Database

If your existence checking policy is check database, then OracleAS TopLink checks the database for existence for all objects 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. OracleAS TopLink does check the database for existence on objects that you have registered with these methods. It automatically performs an insert if registerNewObject is called, or an update if registerExistingObject is called.

Assume Existence

If your existence checking policy is assume existence then all objects registered in a Unit of Work are assumed to exist. OracleAS TopLink always performs an update to the database on all registered objects, even new objects that you registered with registerObject. However, if you use the registerNewObject method on the new object, then OracleAS TopLink performs an insert in the database even though the existence checking policy says assume existence.

Assume Nonexistence

If your existence checking policy is assume nonexistence then all objects registered in a Unit of Work are assumed to be new. OracleAS TopLink always performs an insert to the database, even on objects read from the database. However, if you use the registerExistingObject method on existing objects, OracleAS TopLink performs an update to the database.

Working with Aggregates

Never register aggregate mapped objects in an OracleAS TopLink Unit of Work (you will get an exception if you try). Aggregate cloning and registration occur automatically, based on the owner of the aggregate object. In other words, if you register the owner of an aggregate, then the aggregate is automatically cloned. When you get a working copy of an aggregate owner, its aggregate is also a working copy.

Always use an aggregate within the context of its owner:

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

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

Unregistering Working Clones

The Unit of Work unregisterObject method allows you to unregister a previously registered object from a Unit of Work. An unregistered object is ignored in the Unit of Work, and any uncommitted changes made to the object up to that point are 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 we do not recommend).

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, thereby 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.

Setting Read-Only Classes for a Single Unit of Work

For example, suppose class A owns class B and class C extends class B. You acquire a Unit of Work in which you know only instances of A will change; you know that no class B will be changed. Before registering an instance of B, use this command:

myUnitofWork.addReadOnlyClass(B.class);

Then you can proceed with your transaction: registering A objects, modifying their working copies, and committing the Unit of Work.

At commit time, the Unit of Work does not have to compare backup copy clones with the working copy 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 large.

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

To identify multiple classes as read only, add them to a Vector and use this command:

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

Setting Read-Only Classes for All Units of Work

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.

Read-Only Descriptors

When you declare a class as read-only, the read-only flag extends to its descriptors. You can flag a descriptor as read-only at development time, using either Java code or OracleAS TopLink Mapping 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 flag a descriptor as read-only in OracleAS TopLink Mapping Workbench, select the Read Only check box for a specific descriptor.

For more information, see "Working with Descriptors," in the Oracle Application Server TopLink Mapping Workbench User's Guide.

Using Conforming Queries and Descriptors

This section explains how to include new, changed, or deleted objects in queries within a Unit of Work prior to commit, including:

Using Conforming Queries

Because queries are executed on the database, querying though a Unit of Work does not, by default, include new, uncommitted, objects in a Unit of Work. The Unit of Work does not spend time executing your query against new, uncommitted, objects in the Unit of Work unless you explicitly tell it to.

Assume that a single Pet of type Cat already exists on the database. Examine the code shown in Example 7-24.

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

    Vector allCats = (Vector)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 is:

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

Note that conforming impacts performance. Before you use conforming, make sure that it is actually necessary. For example, consider the alternative described in "Conforming Query Alternatives".

Conforming Query Alternatives

Sometimes you need to provide other code modules with access to new objects created in a Unit of Work. Although you can use conforming to provide this access, 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 pet:

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 properties:

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

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

However, for situations where the quantity of objects is finite and well-known, this simple and more efficient solution is a practical alternative.

Using Conforming Descriptors

The OracleAS TopLink support for conforming queries in the Unit of Work can be specified in the descriptors.

You can flag 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 OracleAS TopLink Mapping Workbench.

You can flag descriptors to always conform in the Unit of Work by calling the method on the descriptor as follows:

descriptor.setShouldAlwaysConformResultsInUnitOfWork(true);

To set this flag in OracleAS TopLink Mapping Workbench, select the Conform Results in Unit Of Work check box for a descriptor.

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 register the original object in a Unit of Work and then use the Unit of Work methods listed in Table 7-2 to merge serialized object changes into the working copy clone. Each method takes the serialized object as an argument.

Table 7-2 Unit of Work Merge Methods

Method Purpose Used When
mergeClone Merges the serialized object and all its privately owned parts (excluding 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 its privately owned parts (including references from it to independent objects) 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 direct attributes of the object or has marked all of the relationships of the object 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. Use with caution: if two different copies of an object are in the same traversal, it merges 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 TopLink remote session (see "Remote Session"). It automatically handles merging and allows you to use a Unit of Work on the client.

You can merge clones with both existing and new objects. Because they 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.

Example 7-25 shows one way to update the original object with the changes contained in the corresponding serialized object (rmiClone) received from a client.

Example 7-25 Merging a Serialized Object

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

Resuming a Unit of Work After Commit

At commit time, a Unit of Work and its contents expire. Do not use the Unit of Work nor its clones, even if the transaction fails and rolls back.

However, OracleAS TopLink offers API methods that enable you to 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 succeeds, the Unit of Work expires. However, if the commit fails, this method does not invalidate the Unit of Work or its clones. This method enables the user to modify the registered objects in a failed Unit of Work and retry the commit.

Example 7-26 illustrates how to use the commitAndResume method.

Example 7-26 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 SQL:

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

Then the commit call produces the 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 of Work 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.

Parallel Units 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.

Nested Units 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.

OracleAS TopLink 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 copies from one Unit of Work are not valid in another Unit 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.

Example 7-27 shows how to use nested Units of Work.

Example 7-27 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 add custom SQL to a Unit of Work at any time by calling the Unit of Work executeNonSelectingCall method, as shown in Example 7-28.

Example 7-28 Using the executeNonSelectingCall Method

uow.executeNonSelectingCall(new SQLCall(mySqlString));

Validating 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, then this violates object transaction isolation and causes OracleAS TopLink 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. OracleAS TopLink offers API methods to toggle validation, as follows:

  • dontPerformValidation: Disables validation

  • performFullValidation: Enables validation

Validating the Unit of Work Before Commit

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 of traversed objects. This may help you more easily find the problem. You can call this method at any time on a Unit of Work.

Controlling the Order of Deletes

"Deleting Objects" explains that OracleAS TopLink always properly orders the SQL based on the mappings and foreign keys in your object model and schema. You can control the order of deletes by:

Using the Unit of Work setShouldPerformDeletesFirst Method

It is possible to tell the Unit of Work to issue deletes before inserts and updates by calling the Unit of Work setShouldPerformDeletesFirst method.

By default, OracleAS TopLink performs inserts and updates first, to ensure that referential integrity is maintained.

If you are replacing an object with unique constraints by deleting it and inserting a replacement, if the insert occurs before the delete, you may raise a constraint violation. In this case, you may need to call setShouldPerformDeletesFirst so that the delete is performed before the insert.

Using the Descriptor addConstraintDependencies Method

The constraints OracleAS TopLink uses 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 OracleAS TopLink using the descriptor addConstraintDependencies(Class) method.

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

There are two possible solutions:

Using deleteAllObjects without addConstraintDependencies

In the first option, do not use privately owned on the one-to-many (object A to object B) relationship. When deleting object A, make sure to delete all of it's object B as well as any object C instances. For example:

uow.deleteObject(existingA);
uow.deleteAllObjects(existingA.getBs());
// delete one of the C's 
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)

Using deleteAllObjects with addConstraintDependencies

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

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

Now the delete code is:

uow.deleteObject(existingA);
uow.deleteAllObjects(existingA.getBs());
// delete one of the C's 
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, object B is deleted before object A and object C. The main difference is that the second option generates fewer SQL statements because it knows that it is deleting the entire set of object B related from object A.

Improving Unit of Work Performance

For best performance when using a Unit of Work, note the following tips:

  • Register objects with a Unit of Work only if objects are eligible for change. If you register objects that will not change, then the Unit of Work needlessly clones and processes those objects.

  • Avoid the cost of existence checking when you are registering a new or existing object (see "Using Registration and Existence Checking").

  • Avoid the cost of change set calculation on a class you know will not change, by telling the Unit of Work that the class is read-only (see "Declaring Read-Only Classes").

  • Avoid the cost of change set calculation on an object read by a ReadAllQuery in a Unit of Work that you do not intend to change, by unregistering the object (see "Unregistering Working Clones").

  • Before using conforming queries, be sure that it is necessary. For alternatives, see "Using Conforming Queries and Descriptors".