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 EclipseLink, transactions are encapsulated by the Unit of Work object. Using the Unit of Work, you can transactionally modify objects directly or by way of a Java 2 Enterprise Edition (J2EE) external transaction controller such as the Java Transaction API (JTA).
Unit Of Work
The EclipseLink Unit of Work simplifies transactions and improves transactional performance. It is the preferred method of writing to a database in EclipseLink 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
- reduces database traffic by isolating transaction operations in their own memory space
- optimizes cache synchronization, in applications that use multiple caches, by passing change sets (rather than objects) between caches
- isolates object modifications in their own transaction space to allow parallel transactions on the same objects
- ensures referential integrity and minimizes deadlocks by automatically maintaining SQL ordering
- orders database inserts, updates, and deletes to maintain referential integrity for mapped objects
- resolves bidirectional references automatically
- frees the application from tracking or recording its changes
- simplifies persistence with persistence by reachability
Acquiring a Unit of Work
This example shows 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.
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.
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();
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)
However the first example is preferred since 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: Using the Registration Step
In this example, a Pet is read prior to a Unit of Work: the variable pet is the cache copy for that Pet. Inside of the Unit of Work, we must register the cache copy to get a working copy. We then modify the working copy and commit the Unit of Work.
// 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();
Modifying an Object: Skipping the Registration Step
In this example, 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. If we wanted to do something with the updated Pet after commit, 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).
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.
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.
Deleting an Object
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)