Entity Beans and Details Objects


The fundamentals of entity beans were discussed in Chapter 5. In this section we consider a particular aspect of the use of entity beans.

Based on performance considerations, it is useful to change the data of an entity bean using interfaces that permit bulk data operations. To this end, the data of an entity bean are collected in a single class. Sun Microsystems recommends this technique in a publication of the J2EE blueprint (see [27]). Classes that collect the data of an entity bean are generally identified by adding the word "details" or "value" to their names. As an example, let us take the entity bean Part, which models a part in a production system and which supports the remote client view. Such a part has the attributes part number, part description, name of the supplier, and price of the part. Listing 9-14 shows the home interface of the Enterprise Bean Part.

Listing 9-14: Interface PartHome.

start example
 package ejb.part; import java.rmi.RemoteException; import javax.ejb.EJBHome; import javax.ejb.CreateException; import javax.ejb.FinderException; public interface PartHome extends EJBHome {     public Part create(String partNumber)         throws CreateException, RemoteException;     public Part findByPrimaryKey(String partNumber)         throws FinderException, RemoteException; } 
end example

A part is identified by its part number. This is assigned when a new entity is generated. The part number serves as primary key, and it is possible to locate particular parts with the part number. Listing 9-15 shows the remote interface of the Part bean.

Listing 9-15: Interface Part.

start example
 package ejb.part; import java.rmi.RemoteException; import javax.ejb.EJBObject; public interface Part extends EJBObject {     public void setPartDetails(PartDetails pd)         throws RemoteException;     public PartDetails getPartDetails()         throws RemoteException; } 
end example

Instead of a large number of get/set methods for each attribute of the entity bean, the remote interface exhibits precisely one get method and exactly one set method. As parameter and return value there is an object of type PartDetails, which contains all the attributes of the entity bean. All attributes of the Enterprise Bean are transmitted bundled, which results in greater efficiency, since for the transmission of attributes only one network access is required. Listing 9-16 shows the implementation of the class PartDetails.

Listing 9-16: Class PartDetails.

start example
 package ejb.part; public class PartDetails implements java.io.Serializable {     String partNumber;     String partDescription;     String supplierName;     float price;     public PartDetails() {     }     public String getPartNumber() {         return partNumber;     }     public void setPartDescription(String desc) {         partDescription = desc;     }     public String getPartDescription() {         return partDescription;     }     public void setSupplierName(String name) {         supplierName = name;     }     public String getSupplierName() {         return supplierName;     }     public void setPrice(float p) {         price = p;     }     public float getPrice() {         return price;     } } 
end example

For the client, the attributes of the Enterprise Bean are transmitted in a PartDetails object into which they have been collected, and the client calls the associated getter/setter methods on the PartDetails object instead of on the Enterprise Bean. It can change the attributes and then transmit them bundled back to the Enterprise Bean to execute the changes. Usually, some simple plausibility checks are made in the get/set methods of a details object. For example, in many cases null is not permitted to be passed as a parameter, or integer values are required to fall within certain limits. This way of proceeding has the advantage that errors can be detected before data are transmitted to the Enterprise Bean. Often, it is difficult to determine the boundary between plausibility checks and business logic; that is, it is difficult to determine which implementations belong to the details object and which to the class of the Enterprise Bean.

Listing 9-17 shows the implementation of the Enterprise Bean class.

Listing 9-17: Class PartBean.

start example
 package ejb.part; import javax.ejb.*; public abstract class PartBean implements EntityBean {     private EntityContext theContext;     private PartDetails theDetails;     /** Creates new PartBean */     public PartBean() {}     //The create method of the home interface     public String ejbCreate(String partNumber)         throws CreateException     {         setPartNumber(partNumber);         theDetails = new PartDetails();         theDetails.partNumber = partNumber;         return null;     }     public void ejbPostCreate(String partNumber)         throws CreateException     {}     //Abstract getter/setter methods     public abstract void setPartNumber(String num);     public abstract String getPartNumber();     public abstract void setPartDescription(String desc);     public abstract String getPartDescription();     public abstract void setSupplierName(String name);     public abstract String getSupplierName();     public abstract void setPrice(float p);     public abstract float getPrice();     //The method of the remote interface     public void setPartDetails(PartDetails pd) {         setPartDescription(pd.getPartDescription());         setSupplierName(pd.getSupplierName());         setPrice(pd.getPrice());         theDetails = pd;     }     public PartDetails getPartDetails() {          return theDetails;     }     //The methods of the javax.ejb.EntityBean interface     public void setEntityContext(EntityContext ctx) {         theContext = ctx;     }     public void unsetEntityContext() {         theContext = null;     }     public void ejbRemove()         throws RemoveException     {}     public void ejbActivate() {     }     public void ejbPassivate() {     }     public void ejbLoad() {         if(theDetails == null) {             theDetails = new PartDetails();         }         theDetails.setPartNumber = this.getPartNumber();         theDetails.setPartDescription =                               this.getPartDescription();         theDetails.setSupplierName = this.getSupplierName();         theDetails.setPrice = this.getPrice();     }     public void ejbStore() {     } } 
end example

The Enterprise Bean contains a reference to a PartDetails object. This is returned when the client requests the attributes of the Enterprise Bean via the method getPartDetails. If the client wishes to change the attributes of the Enterprise Bean, the reference with the new details object is overwritten. When the Enterprise Bean is first generated, the details object contains only the part number. The client gradually provides the Enterprise Bean with the remaining data. Each time the client changes the attributes of the Enterprise Bean, the values are taken from the details object and relayed to the get/set methods, so that the persistence manager can see to the storage of the changed values. If an existing Part bean is loaded, then a new PartDetails object is generated and provided with the appropriate values.

The process just described is recommended by the J2EE blueprint. Here there is an important feature related to the remote client view that we would like to discuss in the remainder of this section.

Suppose that a database contains the product part indicated in Table 9-4.

Table 9-4: Original data of the Part bean.

Part Number

Description

Provider

Price

0815

housing

Provider A

$1.50

Let us further assume that there is an application for managing product parts. Two users wish to use this application to change part 0815. Figure 9-16 shows this situation.

click to expand
Figure 9-16: Use of a details object.

The Enterprise Bean contains a copy of the data from the database; the remote clients contain their own copies of the data in the form of a details object. Suppose that client A changes the supplier to "Supplier C" and stores the data. The altered details object is relayed to the Enterprise Bean, the attributes of the Enterprise Bean are adjusted, and the persistence manager stores the data in the database. Then client B changes the price from $1.50 to $1.60 and stores those data as well. In this case as well, the altered details object of client B is transmitted to the Enterprise Bean, which adjusts the attributes of the Enterprise Bean, which then are stored by the persistence manager in the database. The expected status of the database is shown in Table 9-5.

Table 9-5: Expected data of the Part bean after change.

Part Number

Description

Provider

Price

0815

housing

Provider C

$1.60

But in fact, the actual data are as shown in Table 9-6.

Table 9-6: Actual data of the Part bean after change.

Part Number

Description

Provider

Price

0815

housing

Provider A

$1.60

Client B has overwritten client A's changes, because both had their own copies of the details object, and client B knew nothing about the previous changes made by client A. Such a situation can arise when client A is a user from the purchasing department and is allowed to make changes only to suppliers. Client B could be an employee in the auditing department who is allowed to change only prices. If the Enterprise Bean had separate get/set methods, the state of the database would be as one had expected. Client A would have changed only the supplier, and client B would have changed only the price. However, because of the use of details objects, a client can only change all the attributes of an Enterprise Bean at once. For this reason, we get the behavior just described. Whether this behavior is acceptable depends on the situation. In this section we will show some alternatives that can keep this undesirable situation from arising.

The class PartDetails could be programmed to notice the changes made by the client. For this to happen, a flag would have to be defined for each attribute (for example, in the form of a Boolean variable or through a bit field where each bit represents the state of change of a particular attribute). If the attributes of the Enterprise Bean are reset, then only those attributes are overwritten that have actually been changed in the details object. With this solution one must be sure that the flags are reset after a successful change has been effected. This solution is relatively simple to implement. The integrity of the data is endangered, however, if the flags in the details object are not reset, or not reset at the correct time.

An alternative solution could involve the Enterprise Bean Part triggering an event via a mechanism like the one described in the previous section when data are changed. All currently active clients would receive the event and would know that their local copy of the PartDetails had become invalid. A change to the data without previous updating of the details object would mean that a previous change could be overwritten. This process would be easy to implement, but in an extreme case could lead to high network traffic between the server and the client. This holds especially when this mechanism is used generally with entity beans to avoid conflicts in data storage. This mechanism would be unworkable if the client ignored the event or for some reason had not yet received the event.

Another solution would be to add a time stamp to the class PartDetails. We will look at this alternative in detail and to this end employ a new class, TsPartDetails. Listing 9-18 shows the implementation of this class. The differences between this class and the class PartDetails are shown in boldface.

Listing 9-18: Class TsPartDetails.

start example
 package ejb.part; public class TsPartDetails implements java.io.Serializable {     String partNumber;     String partDescription;     String supplierName;     float price;     private long timestamp = 0;     TsPartDetails() {     }     public boolean isOutDated(TsPartDetails details) {         return details.timestamp < this.timestamp;     }     void updateTimestamp(javax.ejb.EntityContext ctx,                           long tt)     {          if(ctx == null) {              throw new IllegalStateException("ctx == null");          }          timestamp = tt;     }     public String getPartNumber() {         return partNumber;     }     public void setPartDescription(String desc) {        partDescription = desc;     }     public String getPartDescription() {         return partDescription;     }     public void setSupplierName(String name) {          supplierName = name;     }     public String getSupplierName() {         return supplierName;     }     public void setPrice(float p) {         price = p;     }     public float getPrice() {         return price;     }     public String toString() {       StringBuffer sb = new StringBuffer("[[TsPartDetails]");       sb.append("partNumber=").append(partNumber)         .append(";");       sb.append("partDescription=").append(partDescription)         .append(";");       sb.append("supplierName=").append(supplierName)         .append(";");       sb.append("price=").append(price).append(";");       sb.append("]");       return sb.toString();     } } 
end example

The Method isOutdated makes it possible to compare two TsPartDetails objects and determine whether the passed object is older than the object to which it is being compared. This method is used later by the Enterprise Bean to determine whether the passed details object is obsolete in comparison with the given object. The method updateTimestamp will be used later by the Enterprise Bean to update the time stamp of the detail object after a change in attributes. An instance of the entity bean context must be passed to the method to ensure that only one entity bean can call this method. A client cannot legally access an object of type javax.ejb.EntityContext. The time stamp is passed as a variable of type long, which contains the time in milliseconds.

The implementation of the method toString, inherited from java.lang.Object, is introduced here, though it will become relevant only later.

Listing 9-19 shows the changed remote interface of the Enterprise Bean Part.

Listing 9-19: Changed interface Part.

start example
 package ejb.part; import java.rmi.RemoteException; import javax.ejb.EJBObject; public interface Part extends EJBObject {     public TsPartDetails setPartDetails(TsPartDetails pd)          throws RemoteException, OutOfDateException;     public TsPartDetails getPartDetails()          throws RemoteException; } 
end example

The return value of the method setPartDetails is no longer void, but of type TsPartDetails. This is necessary because for reasons of integrity only one entity bean can update the time stamp of the details object. When the setPartDetails method is called, the client passes a copy of the details object (with RMI, objects are generally passed as call by value). The time stamp of the details object referenced by the Enterprise Bean will indeed be updated, but not the time stamp of the detail object that the client references. So that the client will not have to call the method getPartDetails immediately after a successful call to the method setPartDetails in order to obtain possession of a current TsPartDetails object, the updated details object is returned immediately upon a call to the method setPartDetails. In this way a method call on the Enterprise Bean is avoided.

In order for this mechanism to function correctly, the time stamp of the last change must be stored with the data of the Enterprise Bean in the database. Many EJB containers use several instances of an entity bean identity to be able, for example, to execute incoming method calls in parallel running in their own transactions to a product part with, say, number 0815.

Listing 9-20 shows the new class PartBean. The changed parts of the code are shown in boldface.

Listing 9-20: Changed class PartBean.

start example
 package ejb.part; import javax.ejb.*; public abstract class PartBean implements EntityBean {     private EntityContext theContext = null;     private TsPartDetails theDetails = null;     public PartBean() {}     //The create method of the home interface     public String ejbCreate(String partNumber)         throws CreateException     {         this.setPartNumber(partNumber);         theDetails = new TsPartDetails();         theDetails.partNumber = partNumber;         theDetails.partDescription = "";         theDetails.supplierName = "";         theDetails.price = 0;         long tt = System.currentTimeMillis();         this.setLastModified(tt);         theDetails.updateTimestamp(theContext, tt);         return null;     }     public void ejbPostCreate(String partNumber)         throws CreateException     {}     //abstract getter/setter methods     public abstract void setPartNumber(String num);     public abstract String getPartNumber();     public abstract void setPartDescription(String desc);     public abstract String getPartDescription();     public abstract void setSupplierName(String name);     public abstract String getSupplierName();     public abstract void setPrice(float p);     public abstract float getPrice();     public abstract long getLastModified();     public abstract void setLastModified(long tt);     //the method of the remote interface     public TsPartDetailssetPartDetails(TsPartDetails pd)         throws OutOfDateException     {         if(theDetails.isOutDated(pd)) {             throw new OutOfDateException();         }         this.setPartDescription(pd.getPartDescription());         this.setSupplierName(pd.getSupplierName());         this.setPrice(pd.getPrice());         long tt = System.currentTimeMillis();         this.setLastModified(tt);         theDetails = pd;         theDetails.updateTimestamp(theContext, tt);         return theDetails;     }     public TsPartDetails getPartDetails() {         return theDetails;     }     //the methods of the javax.ejb.EntityBean interface     public void setEntityContext(EntityContext ctx) {         theContext = ctx;     }     public void unsetEntityContext() {         theContext = null;     }     public void ejbRemove()         throws RemoveException     {}     public void ejbActivate() { }     public void ejbPassivate() { }     public void ejbLoad() {         if(theDetails == null) {             theDetails = new TsPartDetails();         }         theDetails.setPartNumber = this.getPartNumber();         theDetails.setPartDescription = this.getPartDescription();         theDetails.setSupplierName = this.getSupplierName();         theDetails.setPrice = this.getPrice();         long tt = this.getLastModified();         theDetails.updateTimestamp(theContext, tt);     }     public void ejbStore() { } } 
end example

In the method setPartDetails the Enterprise Bean first checks whether the passed details object is obsolete. If the passed object is obsolete, the Enterprise Bean triggers an exception of type OutOfDateException. Otherwise, the entity bean's data, the detail object's time stamp, and the time stamp of the Part bean will be updated. Then the updated details object will be returned to the client. The persistence manager ensures that after the method call the time stamp and other data of the Enterprise Bean are made persistent. The method ejbCreate initializes the time stamp, while the method ejbLoad sets the time stamp according to the persistent state.

The time stamp is not public data of the Part bean. Thus for reasons of efficiency a variable of type long is used instead of a data object. The long variable contains the time of the last change in milliseconds.

Listing 9-21 shows the deployment descriptor of the Part bean.

Listing 9-21: Deployment descriptor of the Part bean.

start example
 <?xml version="1.0" ?> <ejb-jar version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd">   <description>     This deployment descriptor contains information     about the entity bean Part.   </description>   <enterprise-beans>     <entity>       <ejb-name>Part</ejb-name>       <home>ejb.part.PartHome</home>       <remote>ejb.part.Part</remote>       <ejb-class>ejb.part.PartBean</ejb-class>       <persistence-type>Container</persistence-type>       <prim-key-class>java.lang.String</prim-key-class>       <reentrant>False</reentrant>       <cmp-version>2.x</cmp-version>       <abstract-schema-name>PartBean</abstract-schema-name>       <cmp-field>         <description>part number</description>         <field-name>partNumber</field-name>       </cmp-field>       <cmp-field>         <description>part description</description>         <field-name>partDescription</field-name>       </cmp-field>       <cmp-field>         <description>part supplier</description>         <field-name>supplierName</field-name>       </cmp-field>       <cmp-field>         <description>part price</description>         <field-name>price</field-name>       </cmp-field>       <cmp-field>         <description>time stamp of the last change</description>         <field-name>lastModified</field-name>       </cmp-field>       <primkey-field>partNumber</primkey-field>     </entity>   </enterprise-beans>   <assembly-descriptor>     <container-transaction>       <method>         <ejb-name>Part</ejb-name>         <method-name>setPartDetails</method-name>       </method>       <trans-attribute>Required</trans-attribute>     </container-transaction>   </assembly-descriptor> </ejb-jar> 
end example

With this solution we have prevented clients from overwriting each other's changes. If the EJB container uses several instances of an Enterprise Bean identity for parallel processing of a method call in various transactions, it synchronizes the various instances through a call to the methods ejbLoad and ejbStore. Since the time stamp is persistent, this mechanism serves for this case as well.

The implementation shown in Listing 9-20 can lead to problems if several application servers are in use and their system clocks are not synchronized. This problem can be avoided if one delegates the allocation of the time stamp to the database, instead of placing it in the code of the Enterprise Bean. Since as a rule, only one database server comes into play, there will be no time conflicts. Another possibility is the use of a time server, which the application servers use to synchronize themselves. This ensures that the time is the same on all the server computers.

In certain situations the mechanism with the time stamp can result in a client being unable to store its changes because another client always gets in before it. Moreover, a client will have to implement a relatively complex error handling mechanism for the case of an exception of type OutOfDateException. It must obtain a current details object and attempt to combine the updated data with the user input. If that proves to be impossible, the user's changes will be lost, and the input will have to be repeated. To avoid this situation the mechanism with the time stamp can be extended to include a locking mechanism.

When a client locks an Enterprise Bean, the bean is closed to all other clients for write access. Even if a client is in possession of a current details object, it cannot update the data if another client has locked the Enterprise Bean. There are several ways that one might achieve the implementation of such a locking mechanism. The Enterprise Bean could manage the state of the lock itself, or one could provide a central service for the locking of Enterprise Beans. The lock can be persistent (the lock remains in force even with a restarting of the server) or transient (the lock is removed on restart). Implementing a transient locking mechanism in the code of an Enterprise Bean is certainly the simplest variant. In such a case, caution is advised if this Enterprise Bean is put into service in two different EJB container instances. This could happen if a server cluster is used to improve performance. Instead of one EJB container or application server, several identically configured EJB containers or application servers are used that divide up the collection of client requests. An application server or EJB container can process only a limited number of client requests simultaneously. In such a case a transient locking mechanism implemented in the code of an Enterprise Bean is unworkable. In the case of an entity bean the client will not want to lock the Enterprise Bean to itself. Rather, it will want exclusive access to the part number 0815. And in the case of a server cluster this can be available more than once. Even in the case in which the EJB container uses several instances of an entity bean identity to be able to process client requests in separate transactions, a transient locking mechanism implemented in the code of an Enterprise Bean is unworkable. In these cases a more complicated locking mechanism must be implemented.

We shall not go into more detail, on account of the complexity of the subject. However, it should be clear as to what can be achieved with a locking strategy and what issues must be dealt with.

If an Enterprise Bean uses the local instead of the remote client view, then a new problem arises. In the local client view, RMI will be completely ignored, since the (local) clients and the Enterprise Bean are located in the same process. The semantics of passing parameters and return values are no longer call by value, but call by reference. The scenario depicted in Figure 9-16 would look different with the employment of the local client view and unchanged code of the Part bean. There would no longer be three instances of the details object (one per remote client and one referenced by the Enterprise Bean), but only one instance. This one instance would be referenced by the (local) clients and by the Enterprise Bean. If a client changes the details object, then this change also affects the private data of the Enterprise Bean, without a call to the bean interface being implemented. The EJB container also knows nothing of the change. Above all, this has the consequence that the Enterprise Bean could transmit data that do not agree with the current persistent state. To avoid this situation, the Enterprise Bean would have to clone passed details objects before it stores them in an internal reference or outputs them as return value to the client (see Listing 9-22). Then the semantics of the method calls to setPartDetails and getPartDetails in the local client view would be the same as with the remote client view. In this case, the copying of details objects takes place not implicitly through the use of Java RMI, but explicitly via the Enterprise Bean.

Listing 9-22: Cloning in set/getPartDetails.

start example
 ...     public void setPartDetails(PartDetails pd)     {         this.setPartDescription(pd.getPartDescription());         this.setSupplierName(pd.getSupplierName());         this.setPrice(pd.getPrice());         this.theDetails = pd.clone();     }     public PartDetails getPartDetails() {         return this.theDetails.clone();     } ... 
end example

To avoid the cloning of details objects, the details class could be conceived is such a way that it no longer has any set methods (see Listing 9-23). The local client would have to generate a new details object before it could change the data of an Enterprise Bean. It would then pass the newly generated details object to the Enterprise Bean in order to change the Enterprise Bean's data. In this way, the local client no longer has access to the private data of the Enterprise Bean. The problem of two clients (local or remote) being able to overwrite each other's changes is not solved by the read-only PartDetails class. It simply creates a new problem, arising from the call by reference semantics, in connection with the local client view.

Listing 9-23: PartDetails read-only.

start example
 package ejb.part; public class PartDetails implements java.io.Serializable {     String partNumber;     String partDescription;     String supplierName;     float price;     public PartDetails(String partNumber,                        String partDescription,                        String supplierName,                        float price)     {         this.partNumber = partNumber;         this.partDescription = partDescription;         this.supplierName = supplierName;         this.price = price;     }     public String getPartNumber() {         return partNumber;     }     public String getPartDescription() {         return partDescription;     }     public String getSupplierName() {         return supplierName;     }     public float getPrice() {         return price;     } } 
end example

If one decides to implement details objects in order to achieve optimized data transfer between client and Enterprise Bean, then one must be aware of the consequences. Whether the situation portrayed in this chapter is acceptable depends on the requirements of the application. If the situation is unacceptable, then there are several possibilities for improving things. How far one goes, that is, whether one provides the details object with a time stamp, how complex the error-handling algorithms are for an OutOfDateException exception, whether one introduces a locking strategy, whether one uses transient or persistent locks, whether a central locking service is employed, and so on, depends again on the requirements of the application. In deciding on a particular solution one should always weigh the tradeoffs between the complexity of the implementation and the benefits obtained.

Finally, we would like to refer the reader to Section 10.3 of [7], "Optimistic Locking Pattern."




Enterprise JavaBeans 2.1
Enterprise JavaBeans 2.1
ISBN: 1590590880
EAN: 2147483647
Year: 2006
Pages: 103

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net