![]() |
|
|
![]()
|
Composite EntityContextEntity beans are not intended to represent every persistent object in the object model. Entity beans are better suited for coarse-grained persistent business objects. ProblemIn a J2EE application, clients (applications, JSPs, servlets, JavaBeans) 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 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:
Forces
SolutionUse 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. StructureWhile 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.
The sequence diagram in Figure 8.18 shows the interactions for this pattern.
Participants and ResponsibilitiesCompositeEntityCompositeEntity 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 ObjectA 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 DependentObject3A 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. StrategiesThis 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 StrategyIn 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 StrategyIn 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.
The sequence diagram for this strategy is shown in Figure 8.20.
Lazy Loading StrategyA 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 Store Optimization (Dirty Marker) StrategyA common problem with bean-managed persistence occurs when persisting
the complete object graph during an 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
Figure 8.22 contains a sequence diagram showing an example interaction for this strategy.
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 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 In cases where This strategy avoids the huge overhead of having to persist the entire
dependent objects graph to the database whenever the 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 StrategyWith 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.
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
Sample CodeConsider 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:
Implementing the Composite Entity PatternThe 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 StrategyWhen the Composite Entity is first loaded in the 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) StrategyTo use the Store Optimization strategy, the dependent objects need to
have implemented the DirtyMarker interface, as shown in Example 8.20.
The 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; } } ... 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 StrategyNow 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 Example 8.23 Creating the Composite Transfer Object ... public ResourceCompositeTO getResourceDetailsData() { ResourceCompositeTO compositeTO = new ResourceCompositeTO (getResourceData(), getSkillsData(), getBlockOutTimesData()); return compositeTO; } ... Related Patterns
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:
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 ObjectsLet 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.
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 BeanIn 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.
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.
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). |
|||||||||||||||||||||||
© 2001, Core J2EE Patterns, All Rights Reserved. |