Oracle® Application Server TopLink Application Developer's Guide
10g Release 2 (10.1.2) Part No. B15901-01 |
|
Previous |
Next |
This section describes:
The OracleAS TopLink Unit of Work simplifies transactions and improves transactional performance. It is the preferred method of writing to a database in OracleAS TopLink because:
It sends a minimal amount of SQL to the database during the commit by updating only the exact changes down to the field level.
It reduces database traffic by isolating transaction operations in their own memory space.
It optimizes cache synchronization, in applications that use multiple caches, by passing change sets (rather than objects) between caches.
It isolates object modifications in their own transaction space to allow parallel transactions on the same objects.
It ensures referential integrity and minimizes deadlocks by automatically maintaining SQL ordering.
It orders database inserts, updates, and deletes to maintain referential integrity for mapped objects.
It resolves bidirectional references automatically.
It frees the application from tracking or recording its changes.
It simplifies persistence with persistence by reachability (see "Associations: New Source to Existing Target Object").
The Unit of Work is used as follows:
The client application acquires a Unit of Work from a session object.
The client application queries OracleAS TopLink to obtain the cache objects it wants to modify and then registers the cache objects with the Unit of Work.
When the first object is registered, the Unit of Work starts its transaction.
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 (see "Clones and the Unit of Work").
The Unit of Work returns the working clone to the client application.
The client application modifies the working clones.
The client application (or external transaction controller) commits the transaction (see "Commit and Roll Back").
Example 7-1 shows the life cycle in code.
Example 7-1 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); // We 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); // We 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.
The Unit of Work maintains two copies of the original objects registered with it:
Working clones
Backup clones
After you change the working clones and the transaction is committed, the Unit of Work compares the working copy clones to the backup copy clones, and writes any changes to the database. The Unit of Work uses clones to allow parallel Units of Work (see "Nested and Parallel Units of Work") to exist, a requirement in multi-user, three-tier applications.
The OracleAS TopLink cloning process is efficient because it clones only the mapped attributes of registered objects, and stops at indirection objects unless you trigger the indirection. For more information, see "Indirection".
You can customize the cloning process using the descriptor copy policy. For more information, see "Descriptor Copy Policy".
Never use a clone after committing the Unit of Work that the clone is from (even if the transaction fails and rolls back). A clone is a working copy used during a transaction, and as soon as the transaction is committed (successful or not), the clone must not be used. Accessing an uninstantiated clone value holder after a Unit of Work commit raises an exception. The only time you can use a clone after a successful commit is when you use the advanced API described in "Resuming a Unit of Work After Commit".
You can use OracleAS TopLink to create a:
For information and examples on using nested and parallel Units of Work, see "Using a Nested or Parallel 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 enables you to 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 roll back operation of other operations in the parent Unit of Work.
Changes are presented to the database as a single transaction.
A Unit of Work is a Session
, and as such, offers the same set of database access methods as a regular session.
When called from a Unit of Work, these methods access the objects in the Unit of Work, register the selected objects automatically, and return clones.
Although this makes it unnecessary for you to call the registerObject
and registerAllObjects
methods, be aware of the restrictions on registering objects described in "Creating an Object" and "Associations: New Source to Existing Target Object".
As with regular sessions, you use the readObject
and readAllObjects
methods to read objects from the database.
When a Unit of Work transaction is committed, it either succeeds, or fails and rolls back. A commit can be initiated by your application or a J2EE container.
At commit time, the Unit of Work compares the working clones and backup clones to calculate the change set (that is, to determine the minimum changes required). Changes include updates to or deletion of existing objects, and the creation of new objects. The Unit of Work then begins a database transaction and attempts to write the changes to the database. If all changes commit successfully on the database, then the Unit of Work merges the changed objects into the session cache. If any of the changes fail on the database, the Unit of Work rolls back any changes on the database and does not merge changes into the session cache.
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 commit, verify your mapping definitions. The order in which you register objects with the registerObject
method does not affect the commit order.
When your application uses JTA, the Unit of Work commit behaves differently than in a non-JTA application. In most cases, the Unit of Work attaches itself to an external transaction. If no transaction exists, the Unit of Work creates a transaction. This distinction affects commit behavior as follows:
If the Unit of Work attaches to an existing transaction, then the Unit of Work ignores the commit
call. The transaction commits the Unit of Work when the entire external transaction is complete.
If the Unit of Work starts the external transaction, then the transaction treats the Unit of Work commit
call as a request to commit the external transaction. The external transaction then calls its own commit code on the database.
In either case, only the external transaction can call commit
on the database because it owns the database connection.
For more information, see "J2EE Integration".
A Unit of Work commit must succeed or fail as a unit. Failure in writing changes to the database causes the Unit of Work to roll back the database to its previous state. Nothing changes in the database, and the Unit of Work does not merge changes into the session cache.
In a JTA environment, the Unit of Work does not own the database connection. In this case, the Unit of Work sends the roll back call to the external transaction rather than the database, and the external transaction treats the roll back call as a request to roll the transaction back.
For more information, see "J2EE Integration".
You cannot modify the primary key attribute of an object in a Unit of Work. This is an unsupported operation, and doing so will result in unexpected behaviour (exceptions and/or database corruption).
To replace one instance of an object with unique constraints with another, see "Using the Unit of Work setShouldPerformDeletesFirst Method".
This chapter uses the following object model and schema in the examples provided. The example object model appears in Figure 7-2, and the example entity-relationship (data model) diagram appears in Figure 7-3.