Context
Entity beans are not intended to represent every persistent object in
the object model. Entity beans are better suited for coarse-grained persistent
business objects.
Problem
In a Java 2 Platform, Enterprise Edition (J2EE)
application, clients -- such as applications, JavaServer Pages (JSP)
pages, servlets, JavaBeans components --
access entity beans via their remote interfaces. Thus, every client invocation
potentially routes through network stubs and skeletons, even if the client
and the enterprise bean are in the same JVM, OS, or machine. When entity
beans are fine-grained objects, clients tend to invoke more individual
entity bean methods, resulting in high network overhead.
Entity beans represent distributed persistent business objects. Whether
developing or migrating an application to the J2EE platform, object granularity
is very important when deciding what to implement as an entity bean. Entity
beans should represent coarse-grained business objects, such as those
that provide complex behavior beyond simply getting and setting field
values. These coarse-grained objects typically have dependent objects.
A dependent object is an object that has no real domain meaning when not
associated with its coarse-grained parent.
A recurring problem is the direct mapping of the object model to an
Enterprise JavaBeans (EJB)
model (specifically entity beans). This creates a relationship between
the entity bean objects without consideration of coarse-grained versus
fine-grained (or dependent) objects. Determining what to make coarse-grained
versus fine-grained is typically difficult and can best be done via modeling
relationships in Unified Modeling Language (UML) models.
There are a number of areas impacted by the fine-grained entity bean
design approach:
-
Entity Relationships - Directly mapping an object
model to an EJB model does not take into account the impact of relationships
between the objects. The inter-object relationships are directly transformed
into inter-entity bean relationships. As a result, an entity bean
might contain or hold a remote reference to another entity bean. However,
maintaining remote references to distributed objects involves different
techniques and semantics than maintaining references to local objects.
Besides increasing the complexity of the code, it reduces flexibility,
because the entity bean must change if there are any changes in its
relationships.
Also, there is no guarantee as to the validity of the entity bean
references to other entity beans over time. Such references are established
dynamically using the entity's home object and the primary key for
that entity bean instance. This implies a high maintenance overhead
of reference validity checking for each such entity-bean-to-entity-bean
reference.
-
Manageability - Implementing fine-grained objects
as entity beans results in a large number of entity beans in the system.
An entity bean is defined using several classes. For each entity bean
component, the developer must provide classes for the home interface,
the remote interface, the bean implementation, and the primary key.
In addition, the container may generate classes to support the entity
bean implementation. When the bean is created, these classes are realized
as real objects in the container. In short, the container creates
a number of objects to support each entity bean instance. Large numbers
of entity beans result in more classes and code to maintain for the
development team. It also results in a large number of objects in
the container. This can negatively impact the application performance.
-
Network Performance -
Fine-grained entity beans potentially have more inter-entity bean
relationships. Entity beans are distributed objects. When one entity
bean invokes a method on another entity bean, the call is potentially
treated as a remote call by the container, even if both entity beans
are in the same container or JVM. If the number of entity-bean-to-entity-bean
relationships increases, then this decreases system scalability due
to heavy network overhead.
-
Database Schema Dependency
- When the entity beans are fine-grained, each entity bean instance
usually represents a single row in a database. This is not a proper
application of the entity bean design, since entity beans are more
suitable for coarse-grained components. Fine-grained entity bean implementation
typically is a direct representation of the underlying database schema
in the entity bean design. When clients use these fine-grained entity
beans, they are essentially operating at the row level in the database,
since each entity bean is effectively a single row. Because the entity
bean directly models a single database row, the clients become dependent
on the database schema. When the schema changes, the entity bean definitions
must change as well. Further, since the clients are operating at the
same granularity, they must observe and react to this change. This
schema dependency causes a loss of flexibility and increases the maintenance
overhead whenever schema changes are required.
-
Object Granularity (Coarse-Grained versus Fine-Grained)
- Object granularity impacts data transfer between the enterprise
bean and the client. In most applications, clients typically need
a larger chunk of data than one or two rows from a table. In such
a case, implementing each of these fine-grained objects as an entity
bean means that the client would have to manage the relationships
between all these fine-grained objects. Depending on the data requirements,
the client might have to perform many lookups of a number of entity
beans to obtain the required information.
Forces
-
Entity beans are best implemented as coarse-grained
objects due to the high overhead associated with each entity bean.
Each entity bean is implemented using several objects, such as EJB
home object, remote object, bean implementation, and primary key,
and each is managed by the container services.
-
Applications that directly map relational
database schema to entity beans (where each row in a table is represented
by an entity bean instance) tend to have a large number of fine-grained
entity beans. It is desirable to keep the entity beans coarse-grained
and reduce the number of entity beans in the application.
-
Direct mapping of object model to EJB
model yields fine-grained entity beans. Fine-grained entity beans
usually map to the database schema. This entity-to-database row mapping
causes problems related to performance, manageability, security, and
transaction handling. Relationships between tables are implemented
as relationships between entity beans, which means that entity beans
hold references to other entity beans to implement the fine-grained
relationships. It is very expensive to manage inter-entity bean relationships,
because these relationships must be established dynamically, using
the entity home objects and the enterprise beans' primary keys.
-
Clients do not need to know the implementation
of the database schema to use and support the entity beans. With fine-grained
entity beans, the mapping is usually done so that each entity bean
instance maps to a single row in the database. This fine-grained mapping
creates a dependency between the client and the underlying database
schema, since the clients deal with the fine-grained beans and they
are essentially a direct representation of the underlying schema.
This results in tight coupling between the database schema and entity
beans. A change to the schema causes a corresponding change to the
entity bean, and in addition requires a corresponding change to the
clients.
-
There is an increase in chattiness of
applications due to intercommunication among fine-grained entity beans.
Excessive inter-entity bean communication often leads to a performance
bottleneck. Every method call to the entity bean is made via the network
layer, even if the caller is in the same address space as the called
bean (that is, both the client, or caller entity bean, and the called
entity bean are in the same container). While some container vendors
optimize for this scenario, the developer cannot rely on this optimization
in all containers.
-
Additional chattiness can be observed between the client and the
entity beans because the client may have to communicate with many
fine-grained entity beans to fulfill a requirement. It is desirable
to reduce the communication between or among entity beans and to reduce
the chattiness between the client and the entity bean layer.
Solution
Use Composite Entity to model, represent, and manage a set of
interrelated persistent objects rather than representing them as individual
fine-grained entity beans. A Composite Entity bean represents a graph
of objects.
In order to understand this solution, let us first define what is meant
by persistent objects and discuss their relationships.
A persistent object is an object that is stored in some type of data
store. Multiple clients usually share persistent objects. Persistent objects
can be classified into two types: coarse-grained objects and dependent
objects.
A coarse-grained object is self-sufficient. It has its own life cycle
and manages its relationships to other objects. Each coarse-grained object
may reference or contain one or more other objects. The coarse-grained
object usually manages the lifecycles of these objects. Hence, these objects
are called dependent objects. A dependent object can be a simple self-contained
object or may in turn contain other dependent objects.
The life cycle of a dependent object is tightly coupled to the life cycle
of the coarse-grained object. A client may only indirectly access a dependent
object through the coarse-grained object. That is, dependent objects are
not directly exposed to clients because their parent (coarse-grained)
object manages them. Dependent objects cannot exist by themselves. Instead,
they always need to have their coarse-grained (or parent) object to justify
their existence.
Typically, you can view the relationship between a coarse-grained object
and its dependent objects as a tree. The coarse-grained object is the
root of the tree (the root node). Each dependent object can be a standalone
dependent object (a leaf node) that is a child of the coarse-grained object.
Or, the dependent object can have parent-child relationships with other
dependent objects, in which case it is considered a branch node.
A Composite Entity bean can represent a coarse-grained object and all
its related dependent objects. Aggregation combines interrelated persistent
objects into a single entity bean, thus drastically reducing the number
of entity beans required by the application. This leads to a highly coarse-grained
entity bean that can better leverage the benefits of entity beans than
can fine-grained entity beans.
Without the Composite Entity approach, there is a tendency to view each
coarse-grained and dependent object as a separate entity bean, leading
to a large number of entity beans.
Structure
While there are many strategies in implementing the Composite Entity
pattern, the first one we discuss is represented by the class diagram
in Figure 8.17. Here the Composite Entity contains the coarse-grained
object, and the coarse-grained object contains dependent objects.
Figure 8.17 Composite Entity class diagram
The sequence diagram in Figure 8.18 shows the interactions for this pattern.
Figure 8.18 Composite Entity sequence diagram
Participants and Responsibilities
CompositeEntity
CompositeEntity is the coarse-grained entity bean. The CompositeEntity
may be the coarse-grained object, or it may hold a reference to the coarse-grained
object. The "Strategies" section explains the different implementation
strategies for a Composite Entity.
Coarse-Grained Object
A coarse-grained object is an object that has its own life cycle and
manages its own relationships to other objects. A coarse-grained object
can be a Java object contained in the Composite Entity. Or, the Composite
Entity itself can be the coarse-grained object that holds dependent objects.
These strategies are explained in the "Strategies" section.
DependentObject1, DependentObject2, and DependentObject3
A dependent object is an object that depends on the coarse-grained object
and has its life cycle managed by the coarse-grained object. A dependent
object can contain other dependent objects; thus there may be a tree of
objects within the Composite Entity.
Strategies
This section explains different strategies for implementing a Composite
Entity. The strategies consider possible alternatives and options for
persistent objects (coarse-grained and dependent) and the use of Transfer
Objects.
Composite Entity Contains Coarse-Grained Object Strategy
In this strategy, the Composite Entity holds or contains the coarse-grained
object. The coarse-grained object continues to have relationships with
its dependent objects. The structure section of this pattern describes
this as the main strategy.
Composite Entity Implements Coarse-Grained Object Strategy
In this strategy, the Composite Entity itself is the coarse-grained object
and it has the coarse-grained object's attributes and methods. The dependent
objects are attributes of the Composite Entity. Since the Composite Entity
is the coarse-grained object, the entity bean expresses and manages all
relationships between the coarse-grained object and the dependent objects.
Figure 8.19 is the class diagram for this strategy.
Figure 8.19 Composite Entity Implements Coarse-Grained Object class
diagram
The sequence diagram for this strategy is shown in Figure 8.20.
Figure 8.20 Composite Entity Implements Coarse-Grained Object sequence
diagram
Lazy Loading Strategy
A Composite Entity can be composed of many levels of dependent objects
in its tree of objects. Loading all the dependent objects when the Composite
Entity's ejbLoad() method is called by the EJB Container
may take considerable time and resources. One way to optimize this is
by using a lazy loading strategy for loading the dependent objects. When
the ejbLoad() method is called, at first only load those
dependent objects that are most crucial to the Composite Entity clients.
Subsequently, when the clients access a dependent object that has not
yet been loaded from the database, the Composite Entity can perform a
load on demand. Thus, if some dependent objects are not used, they are
not loaded on initialization. However, when the clients subsequently need
those dependent objects, they get loaded at that time. Once a dependent
object is loaded, subsequent container calls to the ejbLoad() method
must include those dependent objects for reload to synchronize the changes
with the persistent store.
Store Optimization (Dirty Marker) Strategy
A common problem with bean-managed persistence occurs when persisting
the complete object graph during an ejbStore() operation.
Since the EJB Container has no way of knowing what data has changed in
the entity bean and its dependent objects, it puts the burden on the developer
to determine what and how to persist the data. Some EJB containers provide
a feature to identify what objects in Composite Entity's graph need to
be stored due to a prior update. This may be done by having the developers
implement a special method in the dependent objects, such as isDirty() ,
that is called by the container to check if the object has been updated
since the previous ejbStore() operation.
A generic solution may be to use an interface, DirtyMarker, as shown
in the class diagram in Figure 8.21. The idea is to have dependent objects
implement the DirtyMarker interface to let the caller (typically the ejbStore()
method) know if the state of the dependent object has changed. This way,
the caller can choose to obtain the data for subsequent storage.
Figure 8.21 Store Optimization Strategy class diagram
Figure 8.22 contains a sequence diagram showing an example interaction
for this strategy.
Figure 8.22 Store Optimization Strategy sequence diagram
The client performs an update to the Composite Entity, which results
in a change to DependentObject3. DependentObject3 is accessed via its
parent DependentObject2. The Composite Entity is the parent of DependentObject2.
When this update is performed, the setDirty() method is invoked
in the DependentObject3. Subsequently, when the container invokes the
ejbStore() method on this Composite Entity instance, the
ejbStore() method can check which dependent objects have
gone dirty and selectively save those changes to the database. The dirty
marks are reset once the store is successful.
The DirtyMarker interface can also include methods that can recognize
other persistence status of the dependent object. For example, if a new
dependent object is included into the Composite Entity, the ejbStore()
method should be able to recognize what operation to use-in this case,
the dependent object is not dirty, but is a new object. By extending the
DirtyMarker interface to include a method called isNewz() ,
the ejbStore() method can invoke an insert operation instead
of an update operation. Similarly, by including a method called isDeleted (),
the ejbStore() method can invoke delete operation as required.
In cases where ejbStore() is invoked with no intermediate
updates to the Composite Entity, none of the dependent objects have been
updated.
This strategy avoids the huge overhead of having to persist the entire
dependent objects graph to the database whenever the ejbStore()
method is invoked by the container.
Note
The
EJB 2.0 specification addresses the Lazy Loading strategy and the Store
Optimization strategy. The 2.0 specification is in final draft at the
time of this writing. However, it is possible to use these strategies
in pre-EJB 2.0 implementations. Please follow the EJB 2.0 developments
to understand how these strategies will be finalized in the specification.
Composite Transfer Object Strategy
With a Composite Entity, a client can obtain all required information
with just one remote method call. Because the Composite Entity either
implements or holds the coarse-grained object and the hierarchy (or tree)
of dependent objects, it can create the required Transfer Object and return
it to the client by applying the Transfer Object pattern (see "Transfer
Object" on page 261). The sequence diagram for this strategy is shown
in Figure 8.23.
Figure 8.23 Composite Transfer Object Strategy sequence diagram
The Transfer Object can be a simple object or a composite object that
has subobjects (a graph), depending on the data requested by the client.
The Transfer Object is serializable and it is passed by value to the client.
The Transfer Object functions only as a data transfer object; it has no
responsibility with respect to security, transaction, and business logic.
The Transfer Object packages all information into one object, obtaining
the information with one remote call rather than multiple remote calls.
Once the client receives the Transfer Object, all further calls from the
client to the Transfer Object are local to the client.
This discussion points to how the entity can package all its data into
a composite Transfer Object and return it to the client. However, this
strategy also allows the entity bean to return only the required data
to the client. If the client needs data only from a subset of dependent
objects, then the composite Transfer Object returned can contain data
derived from only those required parts and not from all the dependent
objects. This would be an application of the Multiple Transfer Objects
Strategy from the Transfer Object pattern (see "Transfer Object"
on page 261).
Consequences
-
Eliminates Inter-Entity Relationships
Using the Composite Entity pattern, the dependent objects are composed
into a single entity bean, eliminating all inter-entity-bean relationships.
This pattern provides a central place to manage both relationships
and object hierarchy.
-
Improves Manageability by Reducing
Entity Beans
As discussed, implementing persistent objects as fine-grained entity
beans results in a large number of classes that need to be developed
and maintained. Using a Composite Entity reduces the number of EJB
classes and code, and makes maintenance easier. It improves the manageability
of the application by having fewer coarse-grained components instead
of many more fine-grained components.
-
Improves Network Performance
Aggregation of the dependent objects improves overall performance.
Aggregation eliminates all fine-grained communications between dependent
objects across the network. If each dependent object were designed
as a fine-grained entity bean, a huge network overhead would result
due to inter-entity bean communications.
-
Reduces Database Schema Dependency
When the Composite Entity pattern is used, it results in coarse-grained
entity bean implementations. The database schema is hidden from the
clients, since the mapping of the entity bean to the schema is internal
to the coarse-grained entity bean. Changes to the database schema
may require changes to the Composite Entity beans. However, the clients
are not affected since the Composite Entity beans do not expose the
schema to the external world.
-
Increases Object Granularity
With a Composite Entity, the client typically looks up a single entity
bean instead of a large number of fine-grained entity beans. The client
requests the Composite Entity for data. The Composite Entity can create
a composite Transfer Object that contains all the data from the entity
bean and return the Transfer Object to the client in a single remote
method call. This reduces the chattiness between the client and the
business tier.
-
Facilitates Composite Transfer
Object Creation
By using this strategy, chattiness of the communication between the
client and the entity bean is reduced, since the Composite Entity
bean can return a composite Transfer Object by providing a mechanism
to send serialized Transfer Objects from the Composite Entity bean.
Although a Transfer Object returns all data in one remote call, the
amount of data returned with this one call is much larger than the
amount of data returned by separate remote calls to obtain individual
entity bean properties. This trade-off works well when the goal is
to avoid repeated remote calls and multiple lookups.
-
Overhead of Multi-level Dependent Object Graphs
If the dependent objects graph managed by the Composite Entity has
many levels, then the overhead of loading and storing the dependent
objects increases. This can be reduced by using the optimization strategies
for load and store, but then there may be an overhead associated with
checking the dirty objects to store and loading the required objects.
Sample Code
Consider a Professional Service Automation application (PSA) where a
Resource business object is implemented using the Composite Entity pattern.
The Resource represents the employee resource that is assigned to projects.
Each Resource object can have different dependent objects as follows:
-
BlockOutTime-This dependent object represents
the time period the Resource is unavailable for reasons such as training,
vacation, timeoffs, etc. Since each resource can have multiple blocked
out times, the Resource-to-BlockOutTime relationship is a one-to-many
relationship.
-
SkillSet-This dependent object represents the Skill that a Resource
possesses. Since each resource can have multiple skills, the Resource-to-SkillSet
relationship is a one-to-many relationship.
Implementing the Composite Entity Pattern
The pattern for the Resource business object is implemented as a Composite
Entity (ResourceEntity), as shown in Example 8.18. The one-to-many relationship
with its dependent objects (BlockOutTime and SkillSet objects) are implemented
using collections.
Example 8.18 Entity Implements Coarse-Grained Object
package corepatterns.apps.psa.ejb;
import corepatterns.apps.psa.core.*;
import corepatterns.apps.psa.dao.*;
import java.sql.*;
import javax.sql.*;
import java.util.*;
import javax.ejb.*;
import javax.naming.*;
public class ResourceEntity implements EntityBean {
public String employeeId;
public String lastName;
public String firstName;
public String departmentId;
public String practiceGroup;
public String title;
public String grade;
public String email;
public String phone;
public String cell;
public String pager;
public String managerId;
// Collection of BlockOutTime Dependent objects
public Collection blockoutTimes;
// Collection of SkillSet Dependent objects
public Collection skillSets;
...
private EntityContext context;
// Entity Bean methods implementation
public String ejbCreate(ResourceTO resource) throws
CreateException {
try {
this.employeeId = resource.employeeId;
setResourceData(resource);
getResourceDAO().create(resource);
} catch(Exception ex) {
throw new EJBException("Reason:" + ...);
}
return this.employeeId;
}
public String ejbFindByPrimaryKey(String primaryKey)
throws FinderException {
boolean result;
try {
ResourceDAO resourceDAO = getResourceDAO();
result =
resourceDAO.selectByPrimaryKey(primaryKey);
} catch(Exception ex) {
throw new EJBException("Reason:" + ...);
}
if(result) {
return primaryKey;
}
else {
throw new ObjectNotFoundException(...);
}
}
public void ejbRemove() {
try {
// Remove dependent objects
if(this.skillSets != null) {
SkillSetDAO skillSetDAO = getSkillSetDAO();
skillSetDAO.setResourceID(employeeId);
skillSetDAO.deleteAll();
skillSets = null;
}
if(this.blockoutTime != null) {
BlockOutTimeDAO blockouttimeDAO =
getBlockOutTimeDAO();
blockouttimeDAO.setResourceID(employeeId);
blockouttimeDAO.deleteAll();
blockOutTimes = null;
}
// Remove the resource from the persistent store
ResourceDAO resourceDAO = new
ResourceDAO(employeeId);
resourceDAO.delete();
} catch(ResourceException ex) {
throw new EJBException("Reason:"+...);
} catch(BlockOutTimeException ex) {
throw new EJBException("Reason:"+...);
} catch(Exception exception) {
...
}
}
public void setEntityContext(EntityContext context)
{
this.context = context;
}
public void unsetEntityContext() {
context = null;
}
public void ejbActivate() {
employeeId = (String)context.getPrimaryKey();
}
public void ejbPassivate() {
employeeId = null;
}
public void ejbLoad() {
try {
// load the resource info from
ResourceDAO resourceDAO = getResourceDAO();
setResourceData((ResourceTO)
resourceDAO.load(employeeId));
// Load other dependent objects, if necessary
...
} catch(Exception ex) {
throw new EJBException("Reason:" + ...);
}
}
public void ejbStore() {
try {
// Store resource information
getResourceDAO().update(getResourceData());
// Store dependent objects as needed
...
} catch(SkillSetException ex) {
throw new EJBException("Reason:" + ...);
} catch(BlockOutTimeException ex) {
throw new EJBException("Reason:" + ...);
}
...
}
public void ejbPostCreate(ResourceTO resource) {
}
// Method to Get Resource Transfer Object
public ResourceTO getResourceTO() {
// create a new Resource Transfer Object
ResourceTO resourceTO = new
ResourceTO(employeeId);
// copy all values
resourceTO.lastName = lastName;
resourceTO.firstName = firstName;
resourceTO.departmentId = departmentId;
...
return resourceTO;
}
public void setResourceData(ResourceTO resourceTO) {
// copy values from Transfer Object into entity bean
employeeId = resourceTO.employeeId;
lastName = resourceTO.lastName;
...
}
// Method to get dependent Transfer Objects
public Collection getSkillSetsData() {
// If skillSets is not loaded, load it first.
// See Lazy Load strategy implementation.
return skillSets;
}
...
// other get and set methods as needed
...
// Entity bean business methods
public void addBlockOutTimes(Collection moreBOTs)
throws BlockOutTimeException {
// Note: moreBOTs is a collection of
// BlockOutTimeTO objects
try {
Iterator moreIter = moreBOTs.iterator();
while(moreIter.hasNext()) {
BlockOutTimeTO botTO = (BlockOutTimeTO)
moreIter.next();
if (! (blockOutTimeExists(botTO))) {
// add BlockOutTimeTO to collection
botTO.setNew();
blockOutTime.add(botTO);
} else {
// BlockOutTimeTO already exists, cannot add
throw new BlockOutTimeException(...);
}
}
} catch(Exception exception) {
throw new EJBException(...);
}
}
public void addSkillSet(Collection moreSkills)
throws SkillSetException {
// similar to addBlockOutTime() implementation
...
}
...
public void updateBlockOutTime(Collection updBOTs)
throws BlockOutTimeException {
try {
Iterator botIter = blockOutTimes.iterator();
Iterator updIter = updBOTs.iterator();
while (updIter.hasNext()) {
BlockOutTimeTO botTO = (BlockOutTimeTO)
updIter.next();
while (botIter.hasNext()) {
BlockOutTimeTO existingBOT =
(BlockOutTimeTO) botIter.next();
// compare key values to locate BlockOutTime
if (existingBOT.equals(botTO)) {
// Found BlockOutTime in collection
// replace old BlockOutTimeTO with new one
botTO.setDirty(); //modified old dependent
botTO.resetNew(); //not a new dependent
existingBOT = botTO;
}
}
}
} catch (Exception exc) {
throw new EJBException(...);
}
}
public void updateSkillSet(Collection updSkills)
throws CommitmentException {
// similar to updateBlockOutTime...
...
}
...
} |
Implementing the Lazy Loading Strategy
When the Composite Entity is first loaded in the ejbLoad()
method by the container, let us assume that only the resource data is
to be loaded. This includes the attributes listed in the ResourceEntity
bean, excluding the dependent object collections. The dependent objects
can then be loaded only if the client invokes a business method that needs
these dependent objects to be loaded. Subsequently, the ejbLoad()
needs to keep track of the dependent objects loaded in this manner and
include them for reloading.
The relevant methods from the ResourceEntity class are shown in Example
8.19.
Example 8.19 Implementing Lazy Loading Strategy
...
public Collection getSkillSetsData() {
throws SkillSetException {
checkSkillSetLoad();
return skillSets;
}
private void checkSkillSetLoad()
throws SkillSetException {
try {
// Lazy Load strategy...Load on demand
if (skillSets == null)
skillSets =
getSkillSetDAO(resourceId).loadAll();
} catch(Exception exception) {
// No skills, throw an exception
throw new SkillSetException(...);
}
}
...
public void ejbLoad() {
try {
// load the resource info from
ResourceDAO resourceDAO = new
ResourceDAO(employeeId);
setResourceData((ResourceTO)resourceDAO.load());
// If the lazy loaded objects are already
// loaded, they need to be reloaded.
// If there are not loaded, do not load them
// here...lazy load will load them later.
if (skillSets != null) {
reloadSkillSets();
}
if (blockOutTimes != null) {
reloadBlockOutTimes();
}
...
throw new EJBException("Reason:"+...);
}
}
... |
Implementing the Store Optimization (Dirty Marker)
Strategy
To use the Store Optimization strategy, the dependent objects need to
have implemented the DirtyMarker interface, as shown in Example 8.20.
The ejbStore() method to optimize using this strategy is
listed in Example 8.21.
Example 8.20 SkillSet Dependent Object Implements DirtyMarker Interface
public class SkillSetTO implements DirtyMarker,
java.io.Serializable {
private String skillName;
private String expertiseLevel;
private String info;
...
// dirty flag
private boolean dirty = false;
// new flag
private boolean isnew = true;
// deleted flag
private boolean deleted = false;
public SkillSetTO(...) {
// initialization
...
// is new TO
setNew();
}
// get, set and other methods for SkillSet
// all set methods and modifier methods
// must call setDirty()
public setSkillName(String newSkillName) {
skillName = newSkillName;
setDirty();
}
...
// DirtyMarker methods
// used for modified Transfer Objects only
public void setDirty() {
dirty = true;
}
public void resetDirty() {
dirty = false;
}
public boolean isDirty() {
return dirty;
}
// used for new Transfer Objects only
public void setNew() {
isnew = true;
}
public void resetNew() {
isnew = false;
}
public boolean isNew() {
return isnew;
}
// used for deleted objects only
public void setDeleted() {
deleted = true;
}
public boolean isDeleted() {
return deleted;
}
public void resetDeleted() {
deleted = false;
}
} |
Example 8.21 Implementing Store Optimization
...
public void ejbStore() {
try {
// Load the mandatory data
getResourceDAO().update(getResourceData());
// Store optimization for dependent objects
// check dirty and store
// Check and store commitments
if (skillSets != null) {
// Get the DAO to use to store
SkillSetDAO skillSetDAO = getSkillSetDAO();
Iterator skillIter = skillSet.iterator();
while(skillIter.hasNext()) {
SkillSetTO skill =
(SkillSetTO) skillIter.next();
if (skill.isNew()) {
// This is a new dependent, insert it
skillSetDAO.insert(skill);
skill.resetNew();
skill.resetDirty();
}
else if (skill.isDeleted()) {
// delete Skill
skillSetDAO.delete(skill);
// Remove from dependents list
skillSets. remove(skill);
}
else if (skill.isDirty()) {
// Store Skill, it has been modified
skillSetDAO.update(skill);
// Saved, reset dirty.
skill.resetDirty();
skill.resetNew();
}
}
}
// Similarly, implement store optimization
// for other dependent objects such as
// BlockOutTime, ...
...
} catch(SkillSetException ex) {
throw new EJBException("Reason:"+...);
} catch(BlockOutTimeException ex) {
throw new EJBException("Reason:"+...);
} catch(CommitmentException ex) {
throw new EJBException("Reason:"+...);
}
}
... |
Implementing the Composite Transfer Object Strategy
Now consider the requirement where the client needs to obtain all the
data from the ResourceEntity, and not just one part. This can be done
using the Composite Transfer Object Strategy, as shown in Example 8.22.
Example 8.22 Implementing the Composite Transfer Object
public class ResourceCompositeTO {
private ResourceTO resourceData;
private Collection skillSets;
private Collection blockOutTimes;
// Transfer Object constructors
...
// get and set methods
...
} |
The ResourceEntity provides a getResourceDetailsData()
method to return the ResourceCompositeTO composite Transfer Object, as
shown in Example 8.23.
Example 8.23 Creating the Composite Transfer Object
...
public ResourceCompositeTO getResourceDetailsData() {
ResourceCompositeTO compositeTO =
new ResourceCompositeTO (getResourceData(),
getSkillsData(), getBlockOutTimesData());
return compositeTO;
}
... |
Related Patterns
-
Transfer Object
The Composite Entity pattern uses the Transfer Object pattern for
creating the Transfer Object and returning it to the client. The Transfer
Object pattern is used to serialize the coarse-grained and dependent
objects tree, or part of the tree, as required.
-
Session Facade
If dependent objects tend to be entity beans rather than the arbitrary
Java objects, try to use the Session Facade pattern to manage the
inter-entity-bean relationships.
-
Transfer Object Assembler
When it comes to obtaining a composite Transfer Object from the Composite
Entity (see the "Facilitates Composite Transfer Object Creation"
under the "Consequences" section), this pattern is similar
to the Transfer Object Assembler pattern. However, in this case, the
data sources for all the Transfer Objects in the composite are parts
of the Composite Entity itself, whereas for the Transfer Object Assembler,
the data sources can be different entity beans, session beans, DAOs,
Java objects, and so on.
Entity
Bean as a Dependent Object: Issues and Recommendations
Typically,
we design dependent objects as Java objects that have a direct relationship
with the parent coarse-grained object. However, there may be situations
when a dependent object may appear as an entity bean itself. This can
happen:
-
If the dependent object appears to be depending
on two different parent objects (as is the case with association classes).
-
If
the dependent object already exists as an entity bean in the same
application or is imported from a different application.
In
these cases, the lifestyle of the dependent object may not appear to be
directly related to and managed by a single parent coarse-grained object.
So, what do you do when a dependent object is an entity bean? When you
see a dependent object that is not totally dependent on its parent object?
Or when you cannot identify its sole parent object?
Let's
consider each case in a little more detail.
Case 1: The Dependent Object Depends on Two Parent
Objects
Let
us explore this with the following example. A Commitment represents an
association between a Resource and a Project.
Figure
8.24 shows an example class diagram with relationships between Project,
Resource and Commitment.
Figure 8.24 Example: Dependent object with two parent objects
Commitment
is a dependent object. Both Projects and Resources are coarse-grained
objects. Each Project has a one-to-many relationship with Commitment objects.
Each Resource also has a one-to-many relationship with Commitment objects.
So, is Commitment a dependent object of Project or of Resource? The answer
lies in analyzing the interactions for the use cases that involve these
three objects. If you make the Commitment a dependent of the Project,
then when the Resource accesses its list Commitment objects, it has to
do so through the Project object. On the other hand, if the Commitment
is a dependent of a Resource, when the Project accesses its list of Commitment
objects, it has to do so via the Resource. Both these choices will introduce
entity-bean-to-entity-bean relationships in the design.
But,
what if the Commitment is made an entity bean instead of a dependent object?
Then the relationships between the Project and its list of Commitment
objects, and between a Resource and its list of Commitment objects, will
be entity-to-entity bean relationships. This just worsens the problem
in that now there are two entity-bean-to-entity-bean relationships.
Entity-bean-to-entity-bean
relationships are not recommended due to the overhead associated with
managing and sustaining such a relationship.
Case 2: The Dependent Object Already Exists as
an Entity Bean
In
this case, it may seem that one way to model this relationship is to store
the primary key of the dependent object in the coarse-grained object.
When the coarse-grained object needs to access the dependent object, it
results in an entity-bean- to-entity-bean invocation. The class diagram
for this example is shown in Figure 8.25.
Figure 8.25 Dependent Object is an Entity Bean class diagram
The
sequence diagram for this scenario is shown in Figure 8.26. The Composite
Entity uses the dependent object references to look up the required dependent
entity beans. The dependent object in this case is a proxy to the dependent
entity bean, as shown.
Figure 8.26 Dependent Object is an Entity Bean sequence diagram
While
this may address the requirement of using a dependent entity bean from
a parent entity bean, it is not an elegant solution. Instead, to avoid
the complexity of designing and managing inter-entity relationships, consider
using a session bean to help manage the relationships among entity beans.
In our experience, we have found that the Session Facade pattern helps
us to avoid this problem and provides a better way of managing entity-bean-to-entity-bean
relationships.
So,
we recommend avoiding entity-bean-to-entity-bean relationships as a best
practice and to factor out such relationships into a session bean, using
the Session Facade pattern (see "Session Facade" on page 291).
Contact Us© 2001-2002 Sun Microsystems,
Inc. All Rights Reserved.
|