Skip Headers
Oracle TopLink Developer's Guide
10g Release 3 (10.1.3)
B13593-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
 

Troubleshooting a Unit of Work

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

Avoiding 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 the developer incorrectly believes 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 believes 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 102-16. 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.

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 102-16 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

Determining Whether or Not an Object Is the Cache Object

In "Modifying an Object", 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 Example 102-17.

Example 102-17 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

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

Example 102-18 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

This section explains how to handle the following:

Exceptions at Commit Time

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

Example 102-19 shows one way to handle unit of work exceptions:

Example 102-19 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

See Database Exceptions (4002 – 4018) section in the TopLink Exception Reference chapter for more information on 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 "External Transaction Commit Exceptions".

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. Example 102-20 illustrates a query across two levels of relationships, one-to-many and one-to-one.

Example 102-20 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".

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, this violates object transaction isolation, and causes 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. TopLink 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.