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
 

Unit of Work Basics

This section explores the essential Unit of Work API calls most commonly used throughout the development cycle:

For more information about the available methods for the UnitOfWork, see "Advanced Unit of Work", and the Oracle Application Server TopLink API Reference.

Acquiring a Unit of Work

This example illustrates how to acquire a Unit of Work from a client session object.

Server server = 
    (Server) SessionManager.getManager().getSession(
        sessionName, MyServerSession.class.getClassLoader()
    );
Session session = (Session) server.acquireClientSession();
UnitOfWork uow = session.acquireUnitOfWork();

You can acquire a Unit of Work from any session type. Note that you do not need to create a new session and login before every transaction.

The Unit of Work is valid until the commit or release method is called. After a commit or release, a Unit of Work is not valid, even if the transaction fails and is rolled back.

A Unit of Work remains valid after the commitAndResume method is called as described in "Resuming a Unit of Work After Commit".

When using a Unit of Work with JTA, you can also use the advanced API getActiveUnitOfWork method as described in "J2EE Integration".

Creating an Object

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 commit, verify your mapping definitions. The order in which you register objects with the registerObject method does not affect the commit order.

Example 7-2 and Example 7-3 show how to create and persist a simple object (without relationships) using the clone returned by the Unit of Work registerObject method.

Example 7-2 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 7-3 shows a common alternative:

Example 7-3 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 7-2 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.

Modifying an Object

In Example 7-4, a Pet is read prior to a Unit of Work; the variable pet is the cache copy for that Pet. Inside the Unit of Work, we must register the cache copy to get a working copy. You then modify the working copy and commit the Unit of Work.

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

Example 7-5 takes 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 you do not have a handle to the cache copy.

If you want to do something with the updated Pet after commit, you must 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 7-5 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 only register in a Unit of Work the objects that need to be changed.

Associations: New Target to Existing Source Object

There are two ways to associate a new target object with an existing source object with one-to-many and one-to-one relationships:

Deciding which approach to use depends on whether or not your code requires a reference to the cache copy of the new object after the Unit of Work is committed and on how adaptable to change you want your code to be.

Associating without Reference to the Cache Object

Example 7-6 shows the first way of associating a new target with an existing source.

Example 7-6 Associating without Reference to the Cache Object

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

    PetOwner petOwner = new PetOwner();
    petOwner.setId(400);
    petOwner.setName("Donald Smith");
    petOwner.setPhoneNumber("555-1212");

    VetVisit vetVisit = new VetVisit();
    vetVisit.setId(500);
    vetVisit.setNotes("Pet was shedding a lot.");
    vetVisit.setSymptoms("Pet in good health.");
    vetVisit.setPet(petClone);

    petClone.setPetOwner(petOwner);
    petClone.getVetVisits().addElement(vetVisit); 
uow.commit();

This executes the proper SQL:

INSERT INTO PETOWNER (ID, NAME, PHN_NBR) VALUES (400, 'Donald Smith', '555-1212')
UPDATE PET SET PET_OWN_ID = 400 WHERE (ID = 100)
INSERT INTO VETVISIT (ID, NOTES, SYMPTOMS, PET_ID) VALUES (500, 'Pet was shedding a lot.', 'Pet in good health.', 100)

When associating new objects to existing objects, the Unit of Work treats the new object as if it was a clone. That is, after the commit:

petOwner != session.readObject(petOwner)

For a more detailed discussion of this fact, see "Using registerNewObject").

Therefore, after the Unit of Work commit, the variables vetVisit and petOwner no longer point to their respective cache objects: they point at working copy clones.

If you need the cache object after the Unit of Work commit, you must query for it or create the association with a reference to the cache object (as described in "Associating with Reference to the Cache Object").

Associating with Reference to the Cache Object

Example 7-7 shows how to associate a new target with an existing source with reference to the cache object.

Example 7-7 Associating with Reference to the Cache Object

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

    PetOwner petOwner = new PetOwner();
    PetOwner petOwnerClone = (PetOwner)uow.registerObject(petOwner);
    petOwnerClone.setId(400);
    petOwnerClone.setName("Donald Smith");
    petOwnerClone.setPhoneNumber("555-1212");

    VetVisit vetVisit = new VetVisit();
    VetVisit vetVisitClone = (VetVisit)uow.registerObject(vetVisit);
    vetVisitClone.setId(500);
    vetVisitClone.setNotes("Pet was shedding a lot.");
    vetVisitClone.setSymptoms("Pet in good health.");
    vetVisitClone.setPet(petClone);

    petClone.setPetOwner(petOwnerClone);
    petClone.getVetVisits().addElement(vetVisitClone); 
uow.commit();

Now, after the Unit of Work commit:

petOwner == session.readObject(petOwner)

So, you have a handle to the cache copy, rather than a clone, after the commit.

Example 7-8 shows another way to add a new object in a Unit of Work when a bidirectional relationship exists.

Example 7-8 Resolving Issues When Adding New Objects

// Get an employee read from the parent session of the Unit of Work.
Employee manager = (Employee)session.readObject(Employee.class);

// Acquire a Unit of Work.
UnitOfWork uow = session.acquireUnitOfWork();

// Register the manager to get its clone
Employee managerClone = (Employee)uow.registerObject(manager);

// Create a new employee
Employee newEmployee = new Employee();
newEmployee.setFirstName("Spike");
newEmployee.setLastName("Robertson");

/* INCORRECT: Do not associate the new employee with the original manager. This will cause a QueryException when OracleAS TopLink detects this error during  commit. */
//newEmployee.setManager(manager);

/* CORRECT: Associate the new object with the clone. Note that in this example, the setManager method is maintaining the bidirectional managedEmployees relationship and adding the new employee to its managedEmployees. At commit time, the Unit of Work will detect that this is a new object and will take the appropriate action. */
newEmployee.setManager(managerClone);

/* INCORRECT: Do not register the newEmployee: this will create two copies and cause a QueryException when OracleAS TopLink detects this error during commit.*/
//uow.registerObject(newEmployee);

/* CORRECT: 
In the above setManager call, if the managerClone's managedEmployees was not
maintained by the setManager method, then you should call registerObject before the new employee is related to the manager. If in doubt, you could use the  registerNewObject method to ensure that the newEmployee is registered in the Unit of Work. The registerNewObject method registers the object, but does not make a clone. */
uow.registerNewObject(newEmployee);

// Commit the Unit of Work
uow.commit();

Associations: New Source to Existing Target Object

This section describes how to associate a new source object with an existing target object with one-to-many and one-to-one relationships.

OracleAS TopLink follows all relationships of all registered objects (deeply) in a Unit of Work to calculate what is new and what has changed. This is known as persistence by reachablity. "Associations: New Target to Existing Source Object" showed that when you associate a new target with an existing source, you can choose to register the object or not. If you do not register the new object, it is still reachable from the source object (which is a clone, hence it is registered). However, when you need to associate a new source object with an existing target, then you must register the new object. If you do not register the new object, then it is not reachable in the Unit of Work, and OracleAS TopLink will not write it to the database.

For example, imagine that you want to create a new Pet and associate it with an existing PetOwner. The code shown in Example 7-9 will does this:

Example 7-9 Associating a New Source to an Existing Target Object

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

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

This code generates the proper SQL:

INSERT INTO PET (ID, NAME, TYPE, PET_OWN_ID) VALUES (900, 'Larry', 'Lizzard', 400)

In this situation, you should register the new object and work with the working copy of the new object. If you associate the new object with the PetOwner clone without registering, it will not be written to the database. If you want to associate the PetOwner clone with the new Pet object, use the advanced API registerNewObject, as described in "Using registerNewObject".

If you fail to register the clone and accidentally associate the cache version of the existing object with the new object, then OracleAS TopLink generates an error stating that you have associated the cache version of an object ("from a parent session") with a clone from this Unit of Work. You must work with working copies in Units of Work.

Associations: Existing Source to Existing Target Object

This section explains how to associate an existing source object with an existing target object with one-to-many and one-to-one relationships.

As shown in Example 7-10, associating existing objects with each other in a Unit of Work is as simple as associating objects in Java. Remember to work only with working copies of the objects.

Example 7-10 Associating an Existing Source to Existing Target Object

// Associate all VetVisits in the database to a Pet from the database
UnitOfWork uow = session.acquireUnitOfWork();
    Pet existingPetClone = (Pet)uow.readObject(Pet.class);
    Vector allVetVisitClones;
    allVetVisitClones = (Vector)uow.readAllObjects(VetVisit.class);
    Enumeration enum = allVetVisitClones.elements();
    while(enum.hasMoreElements()) {
        VetVisit vetVisitClone =(VetVisit)enum.nextElement();
        existingPetClone.getVetVisits().addElement(vetVisitClone);
        vetVisitClone.setPet(existingPetClone);
    };
uow.commit();

The most common error when associating existing objects is failing to work with the working copies. If you accidentally associate a cache version of an object with a working copy, then you will get an error at commit time indicating that you associated an object from a parent session (the cache version) with a clone from this Unit of Work.

Example 7-11 shows another instance of associating an existing source to an existing target object.

Example 7-11 Associating Existing Objects

// Get an employee read from the parent session of the Unit of Work.
Employee employee = (Employee)session.readObject(Employee.class)

// Acquire a Unit of Work.
UnitOfWork uow = session.acquireUnitOfWork();
Project project = (Project) uow.readObject(Project.class);

/* When associating an existing object (read from the session) with a clone, we must make sure we register the existing object and assign its clone into a Unit of Work. */

/* INCORRECT: Cannot associate an existing object with a Unit of Work clone. A QueryException will be thrown. */
//project.setTeamLeader(employee);

/* CORRECT: Instead register the existing object then associate the clone. */
Employee employeeClone = (Employee)uow.registerObject(employee);
project.setTeamLeader(employeeClone);
uow.commit();

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, OracleAS TopLink deletes the privately owned parts of the object, because those parts cannot exist without the owning object. At commit time, the Unit of Work generates SQL to delete the objects, taking database constraints into account.

When you delete an object, you must take your object model into account. You may need to set references to the deleted object to null (for an example, see "Using privateOwnedRelationship").

This section explains how to delete objects with a Unit of Work, including:

Using privateOwnedRelationship

Relational databases do not have garbage collection like a Java Virtual Machine (JVM) does. To delete an object in Java you dereference the object. To delete a row in a relational database you must explicitly delete it. Rather than tediously manage when to delete data in the relational database, use the mapping attribute privateOwnedRelationship to make OracleAS TopLink manage the garbage collection in the relational database for you.

As shown in Example 7-12, when you create a mapping using Java, use its privateOwnedRelationship method to tell OracleAS TopLink that the referenced object is privately owned; that is, the referenced object cannot exist without the parent object.

Example 7-12 Specifying a Mapping as Privately Owned

OneToOneMapping petOwnerMapping = new OneToOneMapping();
petOwnerMapping.setAttributeName("petOwner");
petOwnerMapping.setReferenceClass(com.top.uowprimer.model.PetOwner.class);
petOwnerMapping.privateOwnedRelationship();
petOwnerMapping.addForeignKeyFieldName("PET.PET_OWN_ID", "PETOWNER.ID");
descriptor.addMapping(petOwnerMapping);

When you create a mapping using the Mapping Workbench, you can select the Privately Owned check box under the General tab.

When you tell OracleAS TopLink that a relationship is privately owned, you are telling it two things:

  • If the source of a privately owned relationship is deleted, then delete the target.

  • If you dereference a target from a source, then delete the target.

Do not configure privately owned relationships to objects that may be shared. An object should not be the target in more than one relationship if it is the target in a privately owned relationship.

The exception to this rule is the case where you have a many-to-many relationship in which a relation object is mapped to a relation table, and is referenced through a one-to-many relationship by both the source and target. In this case, if the one-to-many mapping is configured as privately owned, then when you delete the source, all the association objects are deleted.

Consider the example shown in Example 7-13.

Example 7-13 Private Owned Relationships

// If the Pet-PetOwner relationship is privateOwned
// then the PetOwner will be deleted at uow.commit()
// otherwise, just the foreign key from PET to PETOWNER will
// be set to null.  The same is true for VetVisit.
UnitOfWork uow = session.acquireUnitOfWork();
    Pet petClone = (Pet)uow.readObject(Pet.class);
    petClone.setPetOwner(null);
    VetVisit vvClone =
        (VetVisit)petClone.getVetVisits().firstElement();
    vvClone.setPet(null);
    petClone.getVetVisits().removeElement(vvClone);
uow.commit();

If the relationships from Pet to PetOwner and from Pet to VetVisit are not privately owned, this code produces the following SQL:

UPDATE PET SET PET_OWN_ID = NULL WHERE (ID = 150)
UPDATE VETVISIT SET PET_ID = NULL WHERE (ID = 350)

If the relationships are privately owned, this code produces the following SQL:

UPDATE PET SET PET_OWN_ID = NULL WHERE (ID = 150)
UPDATE VETVISIT SET PET_ID = NULL WHERE (ID = 350)
DELETE FROM VETVISIT WHERE (ID = 350)
DELETE FROM PETOWNER WHERE (ID = 250)

Explicitly Deleting 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 OracleAS TopLink to delete the row representing the object, using the deleteObject API. For example:

Example 7-14 Explicitly Deleting

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

The above code generates the following SQL:

DELETE FROM PET WHERE (ID = 100)

Understanding the Order in Which Objects are Deleted

The Unit of Work does not track changes or the order of operations. It is intended to isolate you from having to modify your objects in the order that the database requires.

By default, at commit time, the Unit of Work orders all inserts and updates using the constraints defined by your schema. After all inserts and updates are done, the Unit of Work issues the necessary delete operations.

Constraints are inferred from one-to-one and one-to-many mappings. If you have no such mappings, you can add additional constraint knowledge to OracleAS TopLink as described in "Controlling the Order of Deletes".