Building your entity beans to be coarse-grained is a common performance optimization. It allows modeling the business objects with plain java classes rather than as entity beans, reducing the inter-remote object communication and transactional overhead associated with entity beans. These plain classes typically have a life cycle dependent on a parent entity bean, do not have a distinct identity of their own and do not need to be referenced remotely by a client. For CMP, these objects are called dependent objects, and EJB 2.0 defines a standard way to define CMP dependent objects, leaving the complex task of persisting them to the underlying application server.
Unfortunately, BMP entity bean developers cannot leverage dependent objects, they are a CMP construct only. However, BMP developers should still be able to create coarse-grained entity beans,
BMP developers should model dependent business data in plain java classes called Dependent Value Objects. Dependent Value Objects are stored inside your entity beans and are created, modified and removed by your entity beans. Unlike dependent objects with CMP, BMP developers must explicitly write the persistence code of dependent value objects.
Dependent Value Objects are similar to Value Objects in that they are plain java classes and are transportable over the network (this is one advantage over CMP dependent objects, which cannot be accessed by clients). Unlike Value Objects, Dependent Value Objects are persistent objects whose life cycle is managed by an entity bean, whereas Value Objects only exist to transport data across the network in bulk, and are typically discarded once data has been read.
An entity bean can store dependent value objects in a one to one, or one to many fashion. For example, consider a Resume. A Resume entity bean would only require one Address dependent value object, whereas it would require multiple Job Entry objects, which could be stored in a Collection inside the Resume entity bean, as in figure 1.
Figure 1: ResumeBean class diagram with Address and Job Entry Dependents
Implementing dependent value objects with BMP is not trivial. A good implementation will feature:
- Dependent value objects that map to tables in a database
- Immutable dependent value objects
- Lazy loading of dependent value objects
- Intelligent lifecycle management (caching updates until ejbStore, only storing new or modified dependent value objects, etc)
- Partitioning of persistence logic from business logic
One of the easiest ways to persist dependent value objects is to simply serialize them to a SQL BLOB column in the table which your entity bean maps to. If dealing with a collection of dependent value objects, the whole collection could be serialized into this column. A better alternative is to persist your dependent value objects to separate tables, using JDBC to map your objects in an attribute-per-column manner. Persisting your dependent value objects in this manner will allow them to be accessible to sql searches and allow you to perform reporting and maintenance directly on the database (you can’t do this if they are stored as blobs). From our Resume example, our Resume entity bean maps to a resumes table in the database, whereas our Address dependent value object maps to an addresses table and our Job Entries map to a jobentries table, as in figure 2. Since our addresses and job entries are dependent value objects, they do not have their own identity outside of their parent Resume entity bean, and thus they do not have their own primary key, instead they are identified by a resumeID column in the addresses and job entries table.
Figure 2: EJB and dependent object DB mapping
Dependent Value Object should be made immutable, that is, they should have no set methods. In CMP, dependent objects cannot be passed to a client, they can only be accessed internally by an entity bean. One advantage to this restriction is that it ensures that any updating of data in an entity bean is done through the entity bean’s own business methods (encapsulation). In BMP, we are not faced with this restriction since dependent value objects are just regular java classes. In order to achieve the same benefit in BMP but still have the flexibility of passing our dependent value objects to clients, dependent value objects should be made immutable. This will ensure that the client doesn’t make changes to the value object assuming that his changes will be instantly reflected on the server, thus forcing the client to perform all changes through the entity beans remote interface.
Lazy Loading is a performance enhancement that allows dependent value objects to be loaded on demand. It avoids the wasteful practice of loading up all of an entity beans dependent value objects inside ejbCreate(), particularly if a client isn’t interested in the dependent data.
Lazy Loading is easy to implement. Each of your dependent value objects or collections can have get methods which:
- Check to see if the internal dependent value object or collection is has been loaded (is null).
- Calls the database and loads up the requested dependent data if not previously loaded
- Passes the requested dependent value object (or collection) to the caller.
Once the dependent value object (or collection) has been read from the database, it can be cached inside the entity bean for future calls, as in our getAddress() and getJobs() in our Resume Bean (see downloadable example source at bottom of pattern).
In a BMP scenario, intelligent life cycle management is the programmers responsibility. Unfortunately, this is not trivial, particularly when dealing with a collection of dependent objects. A lot of life cycle management and database synchronization logic must be written to track dependent value objects as they are created, updated, and deleted. A good BMP implementation will only update the database with dependent objects that have been modified, created or deleted. It will also allow multiple operations (update, delete, etc) on dependent objects within one transaction and only synchronize the final state of an entity bean’s dependent objects with the database at transaction completion (in ejbStore).
Partitioning of persistence logic from business logic. Given the complexities of life cycle management of dependent value objects, many BMP developers choose to mix their persistence logic with their business logic by adding primary keys or dirty flags to dependent value objects, even interlacing their business logic with code necessary to track the life cycle of dependent value objects. To make programming with dependent value objects in BMP as easy as it in CMP, a developer should not add any special fields to his dependent value objects, nor should persistence logic be mixed in with business logic.
So how can a BMP developer achieve intelligent dependent value object lifecycle management and keep persistence logic separate from business logic? The answer is to abstract these details into a special collection class designed to track the lifecycle of dependent value objects that it is storing. Figure 3 illustrates a DependentValueSet, a special implementation of the Java 2 Set Collection. Along with supporting the normal operations of a Set (including add, remove, etc), the collection adds several new methods including an update(oldObject, newObject) method that takes two dependent value objects as a parameters, the original object and the newly updated one. Since dependent value objects (like CMP dependent objects) do not have any identity of their own (no primary key), we require that the original object be passed as a parameter so that our Set will know which object to replace.
Figure 3: The DependentValueSet Class
Using the DependentValueSet allows CMP developers to achieve separation of persistence logic from business logic because business methods need only interact with the normal operations of the Set interface and the extra update method. Dependent Value Objects are also kept clean; they do not need any special attributes added to track their state, as the DependentValueSet handles all of that transparently.
Our DependentValueSet implementation allows for intelligent lifecycle management by tracking which of our dependent objects were created, deleted or modified internally. This is extremely powerful as it allows the user to perform multiple operations on dependent value objects within one transaction, and synchronize the final state of our dependent value objects just once in ejbStore(). For example, the same dependent value object can be updated multiple times, but in ejbStore(), only one database update needs to be performed.
To support database synchronization, our DependentValueSet adds 5 extra methods:
The addObjectFromDatabase method is meant to be called when dependent value objects are initially loaded from the database. It allows the DependentValueSet to track the objects that were initially in the database. The getObjectToXXX methods allow a BMP developer to query the DependentValueSet to find out which objects should be inserted, deleted, or updated, and write the dependent value object specific SQL code to handle these updates in ejbStore(). Once synchronization is complete, the DependentValueSet should be notified (databaseHasBeenUpdated ) so that it can clear itself up for the next transaction.
From our Resume example, we store our collection of job entries in a DependentValueSet. The entity beans remote interface exposes create, read, update, and remove (CRUD) operations for job entries, each of which delegate to methods on the DependentValueSet called jobentries, which handles all the life cycle tracking of the resumes jobs. At the end of the transaction (in ejbStore() ), we query our DependentValueSet to determine which job entries have been created, removed, or updated, and execute the appropriate SQL accordingly.
Each Resume only requires one Address dependent value object, thus we don’t need the advanced services of our DependetValueSet. Instead, we can manually get and set the object, and simply keep around an isAddresssModifed flag so that ejbStore() will know if our Address has been set in this transaction, and store it in the database if so.