Skip Headers
Oracle® Application Development Framework Developer's Guide
10g Release 3 (10.1.3)
B25386-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
 

3.8 Creating and Modifying Objects with a Unit of Work

A database transaction is a set of operations (create, read, update, or delete) that either succeed or fail as a single operation. The database discards, or rolls back, unsuccessful transactions, leaving the database in its original state.

In TopLink, transactions are contained in the unit of work object. You acquire a unit of work from a session and using its API, you can control transactions directly or through a Java 2 Enterprise Edition (J2EE) application server transaction controller such as the Java Transaction API (JTA).

The unit of work isolates changes in a transaction from other threads until it successfully commits the changes to the database. Unlike other transaction mechanisms, the unit of work automatically manages changes to the objects in the transaction, the order of the changes, and changes that might invalidate other TopLink caches. The unit of work manages these issues by calculating a minimal change set, ordering the database calls to comply with referential integrity rules and deadlock avoidance, and merging changed objects into the shared cache. In a clustered environment, the unit of work also synchronizes changes with the other servers in the coordinated cache.

3.8.1 How to Create a Unit of Work

Example 3-7 shows how to acquire a unit of work from a client session object.

Example 3-7 Acquiring a Unit of Work

public UnitOfWork acquireUnitOfWork() {

  Server server = getServer();

  if (server.hasExternalTransactionController()) {
    return server.getActiveUnitOfWork();
  server.acquireUnitOfWork();

}

3.8.1.1 Creating Objects with Unit of Work

When you create new objects in the unit of work, use the registerObject method to ensure that the unit of work writes the objects to the database at commit time.

The unit of work calculates commit order using foreign key information from one-to-one and one-to-many mappings. If you encounter constraint problems during a commit transaction, verify your mapping definitions. The order in which you register objects with the registerObject method does not affect the commit order.

Example 3-8 and Example 3-9 show how to create and persist a simple object (without relationships) using the clone returned by the unit of work registerObject method.

Example 3-8 Creating an Object: Preferred Method

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

Example 3-9 shows a common alternative.

Example 3-9 Creating an Object: Alternative Method

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

Both approaches produce the following SQL:

INSERT INTO PET (ID, NAME, TYPE, PET_OWN_ID) VALUES (100, 'Fluffy', 'Cat', NULL)

Example 3-8 is preferred: it gets you into the pattern of working with clones and provides the most flexibility for future code changes. Working with combinations of new objects and clones can lead to confusion and unwanted results.

3.8.1.2 Typical Unit of Work Usage

TopLink uses the unit of work as follows:

  1. The client application acquires a unit of work from a session object.

  2. The client application queries TopLink to obtain a cache object it wants to modify, and then registers the cache object with the unit of work.

  3. The unit of work registers the object according to the object's change policy.

    By default, as each object is registered, the unit of work accesses the object from the session cache or database and creates a backup clone and working clone. The unit of work returns the working clone to the client application.

  4. The client application modifies the working object returned by the unit of work.

  5. The client application (or external transaction controller) commits the transaction.

  6. The unit of work calculates the change set for each registered object according to the object's change policy.

    By default, at commit time, the unit of work compares the working clones to the backup clones and calculates the change set (that is, determines the minimum changes required). The comparison is done with a backup clone so that concurrent changes to the same objects will not result in incorrect changes being identified. The unit of work then attempts to commit any new or changed objects to the database.

    If the commit transaction succeeds, the unit of work merges changes into the shared session cache. Otherwise, no changes are made to the objects in the shared cache. If there are no changes, the unit of work does not start a new transaction.

Figure 3-14 The Life Cycle of a Unit of Work

The life cycle of a unit of work
Description of "Figure 3-14 The Life Cycle of a Unit of Work"

Example 3-10 shows the default life cycle in code.

Example 3-10 Unit of Work Life Cycle

// The application reads a set of objects from the database 
Vector employees = session.readAllObjects(Employee.class);

// The application specifies an employee to edit
. . .
Employee employee = (Employee) employees.elementAt(index);

try {
    // Acquire a unit of work from the session
    UnitOfWork uow = session.acquireUnitOfWork();

    // Register the object that is to be changed. Unit of work returns a clone
    // of the object and makes a backup copy of the original employee
    Employee employeeClone = (Employee)uow.registerObject(employee);

    // Make changes to the employee clone by adding a new phoneNumber. 
    // If a new object is referred to by a clone, it does not have to be
    // registered. Unit of work determines it is a new object at commit time
    PhoneNumber newPhoneNumber = new PhoneNumber("cell","212","765-9002");
    employeeClone.addPhoneNumber(newPhoneNumber);

    // Commit the transaction: unit of work compares the employeeClone with
    // the backup copy of the employee, begins a transaction, and updates the
    // database with the changes. If successful, the transaction is committed
    // and the changes in employeeClone are merged into employee. If there is an
    // error updating the database, the transaction is rolled back and the
    // changes are not merged into the original employee object
    uow.commit();
} catch (DatabaseException ex) {

    // If the commit fails, the database is not changed. The unit of work should
    // be thrown away and application-specific action taken
}
// After the commit, the unit of work is no longer valid. Do not use further

3.8.2 What Happens when you Modify a Unit of Work

In Example 3-11, a Pet is read prior to a unit of work: the variable pet is the cache copy clone for that Pet. Inside the unit of work, register the cache copy to get a working copy clone. We then modify the working copy clone and commit the unit of work.

Example 3-11 Modifying an Object

// Read in any pet
Pet pet = (Pet)session.readObject(Pet.class);
UnitOfWork uow = session.acquireUnitOfWork();
    Pet petClone = (Pet) uow.registerObject(pet);
    petClone.setName("Furry");
uow.commit();

In Example 3-12, we take advantage of the fact that you can query through a unit of work and get back clones, saving the registration step. However, the drawback is that we do not have a handle to the cache copy clone.

If we wanted to do something with the updated Pet after the commit transaction, we would have to query the session to get it (remember that after a unit of work is committed, its clones are invalid and must not be used).

Example 3-12 Modifying an Object: Skipping the Registration Step

UnitOfWork uow = session.acquireUnitOfWork();
    Pet petClone = (Pet) uow.readObject(Pet.class);
    petClone.setName("Furry");
uow.commit();

Both approaches produce the following SQL:

UPDATE PET SET NAME = 'Furry' WHERE (ID = 100)

Take care when querying through a unit of work. All objects read in the query are registered in the unit of work and therefore will be checked for changes at commit time. Rather than do a ReadAllQuery through a unit of work, it is better for performance to design your application to do the ReadAllQuery through a session, and then register in a unit of work only the objects that need to be changed.

3.8.2.1 Deleting Objects

To delete objects in a unit of work, use the deleteObject or deleteAllObjects method. When you delete an object that is not already registered in the unit of work, the unit of work registers the object automatically.

When you delete an object, TopLink deletes the object's privately owned child parts, because those parts cannot exist without the owning (parent) object. At commit time, the unit of work generates SQL to delete the objects, taking database constraints into account.

3.8.2.1.1 Explicitly Deleting Objects from the Database

If there are cases where you have objects that will not be garbage collected through privately owned relationships (especially root objects in your object model), then you can explicitly tell TopLink to delete the row representing the object using the deleteObject API, as shown in Example 3-13.

Example 3-13 Explicitly Deleting

UnitOfWork uow = session.acquireUnitOfWork();
    pet petClone = (Pet)uow.readObject(Pet.class);
    uow.deleteObject(petClone);
uow.commit();

The preceding code generates the following SQL:

DELETE FROM PET WHERE (ID = 100)

3.8.3 What You May Need to Know

The TopLink unit of work is a powerful transaction model. In addition to the items listed in this section, you should review the "Understanding TopLink Transactions" chapter in the Oracle TopLink Developer's Guide.

3.8.3.1 Unit of Work and Change Policy

The unit of work tracks changes for a registered object based on the change policy you configure for the object's descriptor. If there are no changes, the unit of work will not start a new transaction.

Table 3-1 lists the change policies that TopLink provides.

Table 3-1 TopLink Change Policies

Change Policy Applicable to...

Deferred Change Detection Policy

Wide range of object change characteristics.

The default change policy.

Object-Level Change Tracking Policy

Objects with few attributes or with many attributes and many changed attributes.

Attribute Change Tracking Policy

Objects with many attributes and few changed attributes.

The most efficient change policy.

The default change policy for EJB 3.0 or 2.x CMP on OC4J.


3.8.3.2 Nested and Parallel Units of Work

You can use TopLink to create the following:

3.8.3.2.1 Nested Unit of Work

You can nest a unit of work (the child) within another unit of work (the parent). A nested unit of work does not commit changes to the database. Instead, it passes its changes to the parent unit of work, and the parent attempts to commit the changes at commit time. Nesting units of work lets you break a large transaction into smaller isolated transactions, and ensures that:

  • Changes from each nested unit of work commit or fail as a group.

  • Failure of a nested unit of work does not affect the commit or rollback transaction of other operations in the parent unit of work.

  • Changes are presented to the database as a single transaction.

3.8.3.2.2 Parallel Unit of Work

You can modify the same objects in multiple unit of work instances in parallel because the unit of work manipulates copies of objects. TopLink resolves any concurrency issues when the units of work commits the changes.