Oracle® Application Server TopLink Application Developer's Guide
10g Release 2 (10.1.2) Part No. B15901-01 |
|
Previous |
Next |
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.
This section examines common Unit of Work problems and debugging techniques, including:
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
"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
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
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".
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.
This example examines how to use the registerNewObject
method, including:
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.
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();
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();
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:
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.
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.
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.
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.
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).
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.
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".
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.
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.
This section explains how to include new, changed, or deleted objects in queries within a Unit of Work prior to commit, including:
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".
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.
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.
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.
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)
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
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.
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.
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();
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.
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
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.
"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:
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.
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:
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)
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.
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".