Chapter 11. The Entity-Container Contract
Although each of the three entity type components (EJB 2.0 CMP, EJB 1.1 CMP, and BMP) are programmed differently, their relationships to the container system at runtime are very similar. This chapter covers the relationship between EJBs and their containers. It includes discussions of primary keys, callback methods, and the entity bean life cycle. When differences between the bean types are important, they will be noted. 11.1 The Primary KeyA primary key is an object that uniquely identifies an entity bean. A primary key can be any serializable type, including primitive wrappers (Integer, Double, Long, etc.) or custom classes defined by the bean developer. In the Ship EJB discussed in Chapter 7, Chapter 9, and Chapter 10, we used the Integer type as a primary key. Primary keys can be declared by the bean developer, or the primary key type can be deferred until deployment. We will talk about deferred primary keys later. Because the primary key may be used in remote invocations, it must adhere to the restrictions imposed by Java RMI-IIOP; that is, it must be a valid Java RMI-IIOP value type. These restrictions are discussed in Chapter 5, but for most cases, you just need to make the primary key serializable. In addition, the primary key must implement equals() and hashCode() appropriately. EJB allows two types of primary keys: single-field and compound. Single-field primary keys map to a single persistence field defined in the bean class. The Customer and Ship EJBs, for example, use a java.lang.Integer primary key that maps to the container-managed persistence field named id. A compound primary key is a custom-defined object that contains several instance variables that map to more than one persistence field in the bean class. 11.1.1 Single-Field Primary KeysThe String class and the standard wrapper classes for the primitive data types (java.lang.Integer, java.lang.Double, etc.) can be used as primary keys. These are referred to as single-field primary keys because they are atomic; they map to one of the bean's persistence fields. Compound primary keys map to two or more persistence fields. In the Ship EJB, we specified an Integer type as the primary key: public interface ShipHomeRemote extends javax.ejb.EJBHome { public Ship findByPrimaryKey(java.lang.Integer primarykey) throws FinderException, RemoteException; ... } In this case, there must be a single persistence field in the bean class with the same type as the primary key. For the ShipBean, the id CMP field is of type java.lang.Integer, so it maps well to the Integer primary key type. In EJB 2.0 container-managed persistence, the primary key type must map to one of the bean's CMP fields. The abstract accessor methods for the id field in the ShipBean class fit this description: public class ShipBean implements javax.ejb.EntityBean { public abstract Integer getId(); public abstract void setId(Integer id); ... } The single-field primary key must also map to a CMP field in bean-managed persistence (covered in Chapter 10) and EJB 1.1 container-managed persistence (discussed in Chapter 9). For the ShipBean defined in Chapters Chapter 9 and Chapter 10, the Integer primary key maps to the id instance field: public class ShipBean implements javax.ejb.EntityBean { public Integer id; public String name; ... } With single-field types, you identify the matching persistence field in the bean class by using the <primkey-field> element in the deployment descriptor to specify one of the bean's CMP fields as the primary key. The <prim-key-class> element specifies the type of object used for the primary key class. The Ship EJB uses both of these elements when defining the id persistence field as the primary key: <entity> <ejb-name>ShipEJB</ejb-name> <home>com.titan.ShipHomeRemote</home> <remote>com.titan.ShipRemote</remote> <ejb-class>com.titan.ShipBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <reentrant>False</reentrant> <cmp-field><field-name>id</field-name></cmp-field> <cmp-field><field-name>name</field-name></cmp-field> <cmp-field><field-name>tonnage</field-name></cmp-field> <primkey-field>id</primkey-field> </entity> Although primary keys can be primitive wrappers (Integer, Double, Long, etc.), they cannot be primitive types (int, double, long, etc.) because some of the semantics of EJB interfaces prohibit the use of primitives. For example, the EJBObject.getPrimaryKey() method returns an Object type, thus forcing primary keys to be Objects. Primitives also cannot be primary keys because primary keys must be managed by Collection objects, which work only with Object types. Primitives are not Object types and do not have equals() or hashcode() methods. 11.1.2 Compound Primary KeysA compound primary key is a class that implements java.io.Serializable and contains one or more public fields whose names and types match a subset of persistence fields in the bean class. They are defined by bean developers for specific entity beans. For example, if a Ship EJB didn't have an id field, we might uniquely identify ships by their names and registration numbers. (We are adding the registration CMP field to the Ship EJB for this example.) In this case, the name and registration CMP fields would become our primary key fields, which match corresponding fields (NAME and REGISTRATION) in the SHIP database table. To accommodate multiple fields as a primary key, we need to define a primary key class. The convention in this book is to define all compound primary keys as serializable classes with names that match the pattern BeanNamePK. In this case we can construct a new class called ShipPK, which serves as the compound primary key for our Ship EJB: public class ShipPK implements java.io.Serializable { public String name; public String registration; public ShipPK(){ } public ShipPK(String name, String registration) { this.name = name; this.registration = registration; } public String getName() { return name; } public String getRegistration() { return registration; } public boolean equals(Object obj) { if (obj == null || !(obj instanceof ShipPK)) return false; ShipPK other = (ShipPK)obj; if(this.name.equals(other.name) and this.registration.equals(other.registration)) return true; else return false; } public int hashCode() { return name.hashCode()^registration.hashCode(); } public String toString() { return name+" "+registration; } } To make the ShipPK class work as a compound primary key, we must make its fields public. This allows the container system to use reflection when synchronizing the values in the primary key class with the persistence fields in the bean class. We must also define equals() and hashCode() methods to allow the primary key to be easily manipulated within collections by container systems and application developers. It's important to make sure that the variables declared in the primary key have corresponding CMP fields in the entity bean with matching identifiers (names) and data types. This is required so that the container, using reflection, can match the variables declared in the compound key to the correct CMP fields in the bean class. In this case, the name and registration instance variables declared in the ShipPK class correspond to name and registration CMP fields in the Ship EJB, so it's a good match. We have also overridden the toString() method to return a meaningful value. The default implementation defined in Object returns the class name of the object appended to the object identity for that name space. The ShipPK class defines two constructors: a no-argument constructor and an overloaded constructor that sets the name and registration variables. The overloaded constructor is a convenience method for developers that reduces the number of steps required to create a primary key. The no-argument constructor is required for container-managed persistence. When a new bean is created, the container automatically instantiates the primary key using the Class.newInstance() method and populates it from the bean's container-managed fields. A no-argument constructor must exist in order for this process to work. To accommodate the ShipPK, we change the ejbCreate()/ejbPostCreate() methods so that they have name and registration arguments to set the primary key fields in the bean. Here is how the ShipPK primary key class would be used in the ShipBean class we developed for EJB 2.0 in Chapter 7: import javax.ejb.EntityContext; import javax.ejb.CreateException; public abstract class ShipBean implements javax.ejb.EntityBean { public ShipPK ejbCreate(String name, String registration) { setName(name); setRegistration(registration); return null; } public void ejbPostCreate(String name, String registration) { } ... In EJB 1.1 container-managed persistence, the container-managed fields are set directly. Here is an example of how this would be done with the Ship EJB in CMP 1.1: public class ShipBean implements javax.ejb.EntityBean { public String name; public String registration; public ShipPK ejbCreate(String name, String registration) { this.name = name; this.registration = registration; return null; } In bean-managed persistence, the Ship EJB sets its instance fields, instantiates the primary key, and returns it to the container: public class ShipBean implements javax.ejb.EntityBean { public String name; public String registration; public ShipPK ejbCreate(String name, String registration){ this.name = name; this.registration = registration; ... // database insert logic goes here ... return new ShipPK(name, registration); } The ejbCreate() method now returns the ShipPK as the primary key type. The return type of the ejbCreate() method must match the primary key type if the primary key is defined or the java.lang.Object type if it is undefined. In EJB 2.0 container-managed persistence, if the primary key fields are defined—i.e., if they are accessible through abstract accessor methods—they must be set in the ejbCreate() method. While the return type of the ejbCreate() method is always the primary key type, the value returned must always be null. The EJB container itself takes care of extracting the proper primary key directly. In bean-managed persistence, the bean class is responsible for constructing the primary key and returning it to the container. The ShipHomeRemote interface must be modified so that it uses the name and registration arguments in the create() method and the ShipPK in the findByPrimaryKey() method (EJB requires that we use the primary key type in that method): import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.FinderException; public interface ShipHomeRemote extends javax.ejb.EJBHome { public ShipRemote create(String name, String registration) throws CreateException, RemoteException; public ShipRemote findByPrimaryKey(ShipPK primaryKey) throws FinderException, RemoteException; } setName() and setRegistration(), which modify the name and registration fields of the Ship EJB, should not be declared in the bean's remote or local interfaces. As explained in the next paragraph, the primary key of an entity bean must not be changed once the bean is created. However, methods that simply read the primary key fields (e.g., getName() and getRegistration()) may be exposed because they don't change the key's values. EJB 2.0 specifies that the primary key may be set only once, either in the ejbCreate() method or, if it's undefined, automatically by the container when the bean is created. Once the bean is created, the primary key fields must never be modified by the bean or any of its clients. This is a reasonable requirement that should also be applied to EJB 1.1 CMP and bean-managed persistence beans, because the primary key is the unique identifier of the bean. Changing it could violate referential integrity in the database, possibly resulting in two beans being mapped to the same identifier or breaking any relationships with other beans that are based on the value of the primary key. 11.1.3 Undefined Primary KeysUndefined primary keys for container-managed persistence were introduced in EJB 1.1. Basically, undefined primary keys allow the bean developer to defer declaring the primary key to the deployer, which makes it possible to create more portable entity beans. One problem with container-managed persistence in EJB 1.0 was that the entity bean developer had to define the primary key before the entity bean was deployed. This requirement forced the developer to make assumptions about the environment in which the entity bean would be used, which limited the entity bean's portability across databases. For example, a relational database uses a set of columns in a table as the primary key, to which an entity bean's fields map nicely. An object database, however, uses a completely different mechanism for indexing objects, to which a primary key may not map well. The same is true for legacy systems and Enterprise Resource Planning (ERP) systems. An undefined primary key allows the deployer to choose a system-specific key at deployment time. An object database may generate an object ID, while an ERP system may generate some other primary key. These keys may be automatically generated by the database or backend system. The CMP bean may need to be altered or extended by the deployment tool to support the key, but this is immaterial to the bean developer; she concentrates on the business logic of the bean and leaves the indexing to the container. To facilitate the use of undefined primary keys, the bean class and its interfaces use the Object type to identify the primary key. The Ship EJB developed in Chapter 7 and Chapter 9 could use an undefined primary key. As the following code shows, the Ship EJB's ejbCreate() method returns an Object type: public abstract class ShipBean extends javax.ejb.EntityBean { public Object ejbCreate(String name, int capacity, double tonnage) { ... return null; } The findByPrimaryKey() method defined in the local and remote home interfaces must also use an Object type: public interface ShipHomeRemote extends javax.ejb.EJBHome { public ShipRemote findByPrimaryKey(Object primaryKey) throws javax.ejb.FinderException; } The Ship EJB's deployment descriptor defines its primary key type as java.lang.Object and does not define any <prim-key-field> elements: <ejb-jar> <enterprise-beans> <entity> <ejb-name>ShipEJB</ejb-name> ... <ejb-class>com.titan.ship.ShipBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Object</prim-key-class> <reentrant>False</reentrant> <cmp-field><field-name>name</field-name></cmp-field> <cmp-field><field-name>capacity</field-name></cmp-field> <cmp-field><field-name>tonnage</field-name></cmp-field> </entity> One drawback of using an undefined primary key is that it requires the bean developer and application developer (client code) to work with a java.lang.Object type and not a specific primary key type, which can be limiting. For example, it's not possible to construct an undefined primary key to use in a find method if you don't know its type. This limitation can be quite daunting if you need to locate an entity bean by its primary key. However, entity beans with undefined primary keys can be located easily using other query methods that do not depend on the primary key value, so this limitation is not a serious handicap. In bean-managed persistence, you can declare an undefined primary key simply by making the primary key type java.lang.Object. However, this is pure semantics; the primary key value will not be auto-generated by the container because the bean developer has total control over persistence. In this case the bean developer would still need to use a valid primary key, but its type would be hidden from the bean clients. This method can be useful if the primary key type is expected to change over time. 11.2 The Callback MethodsAll entity beans (container- and bean-managed) must implement the javax.ejb.EntityBean interface. The EntityBean interface contains a number of callback methods that the container uses to alert the bean instance of various runtime events: public interface javax.ejb.EntityBean extends javax.ejb.EnterpriseBean { public abstract void ejbActivate() throws EJBException, RemoteException; public abstract void ejbPassivate() throws EJBException, RemoteException; public abstract void ejbLoad() throws EJBException, RemoteException; public abstract void ejbStore() throws EJBException, RemoteException; public abstract void ejbRemove() throws EJBException, RemoteException; public abstract void setEntityContext(EntityContext ctx) throws EJBException, RemoteException; public abstract void unsetEntityContext() throws EJBException, RemoteException; } Each callback method is invoked on an entity bean instance at a specific time during its life cycle. As described in Chapter 10, BMP beans must implement most of these callback methods to synchronize the bean's state with the database. The ejbLoad() method tells the BMP bean when to read its state from the database; ejbStore() tells it when to write to the database; and ejbRemove() tells the bean when to delete itself from the database. While BMP beans take full advantage of callback methods, CMP entity beans may not need to use all of them. The persistence of CMP entity beans is managed automatically, so in most cases the resources and logic that might be managed by these methods is already handled by the container. However, a CMP entity bean can take advantage of these callback methods if it needs to perform actions that are not automatically supported by the container. You may have noticed that each method in the EntityBean interface throws both a javax.ejb.EJBException and a java.rmi.RemoteException. EJB 1.0 required that a RemoteException be thrown if a system exception occurred while a bean was executing a callback method. However, since EJB 1.1 the use of RemoteException in these methods has been deprecated in favor of the javax.ejb.EJBException. EJB 1.1 and EJB 2.0 suggest that the EJBException be thrown if the bean encounters a system error, such as a SQLException, while executing a method. The EJBException is a subclass of RuntimeException, so you don't have to declare it in the method signature. Since the use of the RemoteException is deprecated, you also don't have to declare it when implementing the callback methods; in fact, it's recommended that you don't. 11.2.1 setEntityContext( ) and unsetEntityContext( )The first method called after a bean instance is instantiated is setEntityContext(). As the method signature indicates, this method passes the bean instance a reference to a javax.ejb.EntityContext, which is the bean instance's interface to the container. The purpose and functionality of the EntityContext is covered in detail later in this chapter. The setEntityContext() method is called prior to the bean instance's entry into the instance pool. In Chapter 3, we discussed the instance pool that EJB containers maintain, where instances of entity and stateless session beans are kept ready to use. EntityBean instances in the instance pool are not associated with any data in the database; their state is not unique. When a client requests a specific entity, an instance from the pool is chosen, populated with data from the database, and assigned to service the client. Any nonmanaged resources needed for the life of the instance should be obtained when this method is called. This ensures that such resources are obtained only once in the life of a bean instance. A nonmanaged resource is one that is not automatically managed by the container (e.g., references to CORBA objects). Only resources that are not specific to the entity bean's identity should be obtained in the setEntityContext() method. Other managed resources (e.g., Java Message Service factories) and entity bean references are obtained as needed from the JNDI ENC. Bean references and managed resources obtained through the JNDI ENC are not available from setEntityContext(). The JNDI ENC is discussed later in this chapter. At the end of the entity bean instance's life, after it is removed permanently from the instance pool and before it is garbage collected, the unsetEntityContext() method is called, indicating that the bean instance is about to be evicted from memory by the container. This is a good time to free up any resources obtained in the setEntityContext() method. 11.2.2 ejbCreate( )In a CMP bean, the ejbCreate() method is called before the bean's state is written to the database. Values passed in to the ejbCreate() method should be used to initialize the CMP fields of the bean instance. Once the ejbCreate() method completes, a new record, based on the persistence fields, is written to the database. In bean-managed persistence, the ejbCreate() method is called when it's time for the bean to add itself to the database. Inside the ejbCreate() method, a BMP bean must use some kind of API to insert its data into the database. Each ejbCreate() method must have parameters that match a create() method in the home interface. If you look at the ShipBean class definition and compare it to the Ship EJB's home interface (see Chapter 7, Chapter 9, and Chapter 10), you can see how the parameters for the create methods match exactly in type and sequence. This enables the container to delegate each create() method on the home interface to the proper ejbCreate() method in the bean instance. In EJB 2.0, the ejbCreate() method can take the form ejbCreate<SUFFIX>(), which allows for easier method overloading when parameters are the same but the methods act differently. For example, ejbCreateByName(String name) and ejbCreateByRegistration(String registration) would have corresponding create() methods defined in the local or home interface, in the form createByName(String name) and createByRegistration(String registration). EJB 1.1 CMP does not allow the use of suffixes on ejbCreate() names. The ejbCreate() and create() methods may differ only by the number and type of parameters defined. The EntityContext maintained by the bean instance does not provide an entity bean with the proper identity until ejbCreate() has completed. This means that while the ejbCreate() method is executing, the bean instance doesn't have access to its primary key or EJB object. The EntityContext does, however, provide the bean with information about the caller's identity and access to its EJB home object (local and remote) and properties. The bean can also use the JNDI naming context to access other beans and resource managers such as javax.sql.DataSource. However, the CMP entity bean developer must ensure that ejbCreate() sets the persistence fields that correspond to the fields of the primary key. When a new CMP entity bean is created, the container will use the CMP fields in the bean class to instantiate and populate a primary key automatically. If the primary key is undefined, the container and database will work together to generate the primary key for the entity bean. Once the bean's state has been populated and its EntityContext has been established, the ejbPostCreate() method is invoked. This method gives the bean an opportunity to perform any postprocessing prior to servicing client requests. In EJB 2.0 CMP entity beans, ejbPostCreate() is used to manipulate container-managed relationship fields. These CMR fields must not be modified by ejbCreate(). The reason for this restriction has to do with referential integrity. The primary key for the entity bean may not be available until after ejbCreate() executes. The primary key is needed if the mapping for the relationship uses it as a foreign key, so assignment of relationships is postponed until ejbPostCreate() completes and the primary key becomes available. This is also true with autogenerated primary keys, which usually require that the insert be done before a primary key can be generated. In addition, referential integrity may specify non-null foreign keys in referencing tables, so the insert must take place first. In reality, the transaction does not complete until both ejbCreate() and ejbPostCreate() have executed, so the vendors are free to choose the best time for database inserts and linking of relationships. The bean identity is not available during the call to ejbCreate(), but it is available in ejbPostCreate(). This means that the bean can access its own primary key and EJB object (local or remote) inside of ejbPostCreate(). This can be useful for performing postprocessing prior to servicing business-method invocations; in CMP 2.0 ejbPostCreate() can be used for initializing CMR fields of the entity bean. Each ejbPostCreate() method must have the same parameters as the corresponding ejbCreate() method, as well as the same method name. For example, if the ShipBean class defines an ejbCreateByName(String name) method, it must also define a matching ejbPostCreateByName(String name) method. The ejbPostCreate() method returns void. In EJB 1.1 CMP, suffixes are not allowed on create methods. Matching the name and parameter lists of ejbCreate() and ejbPostCreate() methods is important for two reasons. First, it indicates which ejbPostCreate() method is associated with which ejbCreate() method. This ensures that the container calls the correct ejbPostCreate() method after ejbCreate() is done. Second, it is possible that one of the parameters passed is not assigned to a persistence field. In this case, you would need to duplicate the parameters of the ejbCreate() method to have that information available in the ejbPostCreate() method. Relationship fields are the primary reason for utilizing the ejbPostCreate() method in EJB 2.0 CMP, because of referential integrity. 11.2.3 ejbCreate( ) and ejbPostCreate( ) Sequence of EventsTo understand how an entity bean instance gets up and running, we have to think of a entity bean in the context of its life cycle. Figure 11-1 shows the sequence of events during a portion of a CMP bean's life cycle, as defined by the EJB specification. Every EJB vendor must support this sequence of events. Figure 11-1. Event sequence for bean-instance creation
The process begins when the client invokes one of the create() methods on the bean's EJB home. A create() method is invoked on the EJB home stub (step 1), which communicates the method to the EJB home across the network (step 2). The EJB home plucks a ShipBean instance from the pool and invokes its corresponding ejbCreate() method (step 3). The create() and ejbCreate() methods are responsible for initializing the bean instance so that the container can insert a record into the database. In the case of the ShipBean, the minimal information required to add a new customer to the system is the customer's unique id. This CMP field is initialized during the ejbCreate() method invocation (step 4). In container-managed persistence (EJB 2.0 and 1.1), the container uses the bean's CMP fields (id, name, tonnage), which it reads from the bean, to insert a record into the database (step 5). Only the fields described as CMP fields in the deployment descriptor are accessed. Once the container has read the CMP fields from the bean instance, it will automatically insert a new record into the database using those fields (step 6).[1] How the data is written to the database is defined when the bean's fields are mapped at deployment time. In our example, a new record is inserted into the CUSTOMER table.
Once the record has been inserted into the database, the bean instance is ready to be assigned to an EJB object (step 7). Once the bean is assigned to an EJB object, the bean's identity is available. This is when ejbPostCreate() is invoked (step 8). In EJB 2.0 CMP entity beans, ejbPostCreate() is used to manage the beans' container-managed relationship fields. This might involve setting the Cruise in the Ship EJB's cruise CMR field or some other relationship (step 9). Finally, when the ejbPostCreate() processing is complete, the bean is ready to service client requests. The EJB object stub is created and returned to the client application, which will use it to invoke business methods on the bean (step 10). 11.2.4 Using ejbLoad( ) and ejbStore( ) in Container-Managed PersistenceThe process of ensuring that the database record and the entity bean instance are equivalent is called synchronization. In container-managed persistence, the bean's CMP fields are automatically synchronized with the database. Persistence in container-managed beans is fairly straightforward, so in most cases we will not need the ejbLoad() and ejbStore() methods. Leveraging the ejbLoad() and ejbStore() callback methods in container-managed beans, however, can be useful if custom logic is needed when synchronizing CMP fields. Data intended for the database can be reformatted or compressed to conserve space; data just retrieved from the database can be used to calculate derived values for nonpersistent fields. Imagine a hypothetical bean class that includes some binary value you want to store in the database. The binary value may be very large (an image, for example), so you may need to compress it before storing it away. Using the ejbLoad() and ejbStore() methods in a container-managed bean allows the bean instance to reformat the data as appropriate for the state of the bean and the structure of the database. Here's how this might work: import java.util.zip.Inflater; import java.util.zip.Deflater; public abstract class HypotheticalBean implements javax.ejb.EntityBean { // Instance variable public byte [] inflatedImage; // CMP field methods public abstract void setImage(byte [] image); public abstract byte [] getImage(); // Business methods. Used by client. public byte [] getImageFile() { if(inflatedImage == null) { Inflater unzipper = new Inflater(); byte [] temp = getImage(); unzipper.setInput(temp); unzipper.inflate(inflatedImage); } return inflatedImage; } public void setImageFile(byte [] image) { inflatedImage = image; } // callback methods public void ejbLoad() { inflatedImage = null; } public void ejbStore() { if(inflatedImage != null) { Deflater zipper = new Deflater(); zipper.setInput(inflatedImage); byte [] temp = new byte[inflatedImage.length]; int size = zipper.deflate(temp); byte [] temp2 = new byte[size]; System.arraycopy(temp, 0, temp2, 0, size); setImage(temp2); } } } Just before the container synchronizes the state of entity bean with the database, it calls the ejbStore() method. This method uses the java.util.zip package to compress the image file, if it has been modified, before writing it to the database. Just after the container updates the fields of the HypotheticalBean with fresh data from the database, it calls ejbLoad(), which reinitializes the inflatedImage instance variable to null. Decompression is preformed lazily, so it's done only when it is needed. Compression is performed by ejbStore() only if the image was accessed; otherwise, the image field is not modified. 11.2.5 Using ejbLoad( ) and ejbStore( ) in Bean-Managed PersistenceIn bean-managed persistence, the ejbLoad() and ejbStore() methods are called by the container when it's time to read from or write to the database. The ejbLoad() method is invoked after the start of a transaction, but before the entity bean can service a method call. The ejbStore() method is usually called after the business method is called, but it must be called before the end of the transaction. While the entity bean is responsible for reading and writing its state from and to the database, the container is responsible for managing the scope of the transaction. This means that the entity bean developer need not worry about committing operations on database-access APIs, provided the resource is managed by the container. The container will take care of committing the transaction and persisting the changes at the appropriate times. If a BMP entity bean uses a resource that is not managed by the container system, the entity bean must manage the scope of the transaction manually, using operations specific to the API. Examples of how to use the ejbLoad() and ejbStore() methods in BMP are shown in detail in Chapter 10. 11.2.6 ejbPassivate( ) and ejbActivate( )The ejbPassivate() method notifies the bean developer that the entity bean instance is about to be pooled or otherwise disassociated from the entity bean identity. This gives the entity bean developer an opportunity to do some last-minute cleanup before the bean is placed in the pool, where it will be reused by some other EJB object. In real-world implementations, the ejbPassivate() method is rarely used, because most resources are obtained from the JNDI ENC and are managed automatically by the container. The ejbActivate() method notifies the bean developer that the entity bean instance has just returned from the pool and is now associated with an EJB object and has been assigned an identity. This gives the entity bean developer an opportunity to prepare the entity bean for service, for example by obtaining some kind of resource connection. As with the ejbPassivate() method, it's difficult to see why this method would be used in practice. It is best to secure resources lazily (i.e., as needed). The ejbActivate() method suggests that some kind of eager preparation can be accomplished, but this is rarely actually done.
One of the few practical reasons for using ejbActivate() is to reinitialize nonpersistent instance fields of the bean class that may have become "dirty" while the instance serviced another client. Regardless of their general usefulness, these callback methods are at your disposal if you need them. In most cases, you are better off using setEntityContext() and unsetEntityContext(), since these methods will execute only once in the life cycle of a bean instance. 11.2.7 ejbRemove( )The component interfaces (remote, local, remote home, and local home) define remove methods that can be used to delete an entity from the system. When a client invokes one of the remove methods, as shown in the following code, the container must delete the entity's data from the database: CustomerHomeRemote customerHome; CustomerRemote customer; customer.remove(); // or customerHome.remove(customer.getPrimaryKey()); The data deleted from the database includes all the CMP fields. So, for example, when you invoke a remove method on a Customer EJB, the corresponding record in the CUSTOMER table is deleted. In CMP 2.0, the remove method also removes the link between the CUSTOMER record and the ADDRESS record. However, the ADDRESS record associated with the CUSTOMER record will not be automatically deleted. The address data will be deleted along with the customer data only if a cascade delete is specified. A cascade delete must be declared explicitly in the XML deployment descriptor, as explained in Chapter 7. The ejbRemove() method in container-managed persistence notifies the entity bean that it's about to be removed and its data is about to be deleted. This notification occurs after the client invokes one of the remove methods defined in a component interface but before the container actually deletes the data. It gives the bean developer a chance to do some last-minute cleanup before the entity is removed. Any cleanup operations that might ordinarily be done in the ejbPassivate() method should also be done in the ejbRemove() method, because the bean will be pooled after the ejbRemove() method completes without having its ejbPassivate() method invoked. In bean-managed persistence, the bean developer is responsible for implementing the logic that removes the entity bean's data from the database. 11.3 EJB 2.0: ejbHome( )In EJB 2.0, CMP and BMP entity beans can declare home methods that perform operations related to the EJB component but that are not specific to an entity bean instance. A home method must have a matching implementation in the bean class with the signature ejbHome<METHOD-NAME>(). For example, the Cruise EJB might define a home method that calculates the total revenue in bookings for a specific Cruise: public interface CruiseHomeLocal extends javax.ejb.EJBLocalHome { public CruiseLocal create(String name, ShipLocal ship); public void setName(String name); public String getName(); public void setShip(ShipLocal ship); public ShipLocal getShip(); public double totalReservationRevenue(CruiseLocal cruise); } Every method in the home interfaces must have a corresponding ejbHome<METHOD-NAME>() in the bean class. For example, the CruiseBean class would have an ejbHomeTotalReservationRevenue() method, as shown in the following code: public abstract class CruiseBean implements javax.ejb.EntityBean { public Integer ejbCreate(String name, ShipLocal ship) { setName(name); } ... public double ejbHomeTotalReservationRevenue(CruiseLocal cruise) { Set reservations = ejbSelectReservations(cruise); Iterator enum = set.iterator(); double total = 0; while(enum.hasNext()) { ReservationLocal res = (ReservationLocal)enum.next(); Total += res.getAmount(); } return total; } public abstract ejbSelectReservations(CruiseLocal cruise); ... } The ejbHome() methods execute without an identity within the instance pool. This is why ejbHomeTotalReservationRevenue() required that a CruiseLocal EJB object reference be passed in to the method. This makes sense once you realize that the caller is invoking the home method on the entity bean's EJB home object and not an entity bean reference directly. The EJB home (local or remote) is not specific to any one entity instance. The bean developer may implement home methods in both EJB 2.0 bean-managed and container-managed persistence implementations typically rely on select methods, while BMP implementations frequently use direct database access and find methods to query data and apply changes. 11.4 EntityContextThe first method called by the container after a bean instance is created is setEntityContext(). This method passes the bean instance a reference to its javax.ejb.EntityContext, which is really the instance's interface to the container. The setEntityContext() method should be implemented by the entity bean developer so that it places the EntityContext reference in an instance field of the bean where it will be kept for the life of the instance. The definition of EntityContext in EJB 2.0 is as follows: public interface javax.ejb.EntityContext extends javax.ejb.EJBContext { public EJBLocalObject getEJBLocalObject() throws IllegalStateException public abstract EJBObject getEJBObject() throws IllegalStateException; public abstract Object getPrimaryKey() throws IllegalStateException; } EJBLocalObject is new to EJB 2.0 and is not supported by EJB 1.1. The definition of the EntityContext in EJB 1.1 is as follows: public interface javax.ejb.EntityContext extends javax.ejb.EJBContext { public abstract EJBObject getEJBObject() throws IllegalStateException; public abstract Object getPrimaryKey() throws IllegalStateException; } As the bean instance is swapped from one EJB object to the next, the information obtainable from the EntityContext changes to reflect the EJB object to which the instance is assigned. This change is possible because the EntityContext is an interface, not a static class definition, and it means that the container can implement the EntityContext with a concrete class that it controls. As the entity bean instance is swapped from one EJB object to another, some of the information made available through the EntityContext will also change. The getEJBObject() method returns a remote reference to the bean instance's EJB object. The getEJBLocalObject() method (EJB 2.0), on the other hand, returns a local reference to the bean instance's EJB object.
The EJB objects obtained from the EntityContext are the same kinds of references that might be used by an application client, in the case of a remote reference, or another co-located bean, in the case of a local reference. These methods allow the bean instance to get its own EJB object reference, which it can then pass to other beans. Here is an example: public class A_Bean extends EntityBean { public EntityContext context; public void someMethod() { B_Bean b = ... // Get a remote reference to B_Bean. EJBObject obj = context.getEJBObject(); A_Bean mySelf = (A_Bean)PortableRemoteObject.narrow(obj,A_Bean.class); b.aMethod( mySelf ); } ... }
In EJB 2.0, the ability of a bean to obtain an EJB object reference to itself is also useful when establishing relationships with other beans in container-managed persistence. For example, the Customer EJB might implement a business method that allows it to assign itself a Reservation: public abstract class CustomerBean implements javax.ejb.EntityBean { public EntityContext context; public void assignToReservation(ReservationLocal reservation) { EJBLocalObject localRef = context.getEJBLocalObject(); Collection customers = reservation.getCustomers(); customers.add(localRef); } ... } The getPrimaryKey() method allows a bean instance to get a copy of the primary key to which it is currently assigned. Use of this method outside of the ejbLoad() and ejbStore() methods of BMP entity beans is probably rare, but the EntityContext makes the primary key available for those unusual circumstances when it is needed. As the context in which the bean instance operates changes, some of the information made available through the EntityContext reference will be changed by the container. This is why the methods in the EntityContext throw the java.lang.IllegalStateException. The EntityContext is always available to the bean instance, but the instance is not always assigned to an EJB object. When the bean is between EJB objects (i.e., when it's in the pool), it has no EJB object or primary key to return. If the getEJBObject(), getEJBLocalObject(), or getPrimaryKey() methods are invoked when the bean is in the pool, they will throw an IllegalStateException. Appendix B provides tables of allowed operations for each bean type describing which EJBContext methods can be invoked at what times. 11.4.1 EJBContextThe EntityContext extends the javax.ejb.EJBContext class, which is also the base class for the SessionContext session beans use. EJBContext defines several methods that provide useful information to a bean at runtime. Here is the definition of the EJBContext interface: package javax.ejb; public interface EJBContext { // EJB home methods public EJBHome getEJBHome(); // EJB 2.0 only public EJBLocalHome getEJBLocalHome(); // security methods public java.security.Principal getCallerPrincipal(); public boolean isCallerInRole(java.lang.String roleName); // transaction methods public javax.transaction.UserTransaction getUserTransaction() throws java.lang.IllegalStateException; public boolean getRollbackOnly() throws java.lang.IllegalStateException; public void setRollbackOnly() throws java.lang.IllegalStateException; // deprecated methods public java.security.Identity getCallerIdentity(); public boolean isCallerInRole(java.security.Identity role); public java.util.Properties getEnvironment(); } The getEJBHome() and getEJBLocalHome() (EJB 2.0) methods return a reference to the bean's EJB home. This is useful if the bean needs to create or find entity beans of its own type. Access to the EJB home proves more useful in bean-managed entity beans and CMP 1.1 entity beans than in CMP 2.0 entity beans, which have select methods and CMR fields. As an example, if all of the employees in Titan's system (including managers) are represented by CMP 1.1 Employee beans, a manager who needs access to subordinate employees can use the getEJBHome() method to get beans representing the appropriate employees: public class EmployeeBean implements EntityBean { int id; String firstName; ... public Enumeration getSubordinates() { Object ref = ejbContext.getEJBHome(); EmployeeHome home = (EmployeeHome) PortableRemoteObject.narrow(ref, EmployeeHome.class); Integer primKey = (Integer)context.getPrimaryKey(); Enumeration subordinates = home.findByManagerID(primKey); return subordinates; } ... } The getCallerPrincipal() method is used to obtain the Principal object representing the client that is currently accessing the bean. The Principal object can, for example, be used by the Ship bean to track the identities of clients making updates: public class ShipBean implements EntityBean { String lastModifiedBy; EntityContext context; ... public void setTonnage(double tons) { tonnage = tons; Principal principal = context.getCallerPrincipal(); String modifiedBy = principal.getName(); } ... } The isCallerInRole() method tells you whether the client accessing the bean is a member of a specific role, identified by a role name. This method is useful when more access control is needed than simple method-based access control can provide. In a banking system, for example, you might allow the Teller role to make most withdrawals but only the Manager role to make withdrawals of over $10,000. This kind of fine-grained access control cannot be addressed through EJB's security attributes because it involves a business logic problem. Therefore, we can use the isCallerInRole() method to augment the automatic access control provided by EJB. First, let's assume that all managers are also tellers. The business logic in the withdraw() method uses isCallerInRole() to make sure that only the Manager role can withdraw sums over $10,000.00: public class AccountBean implements EntityBean { int id; double balance; EntityContext context; public void withdraw(Double withdraw) throws AccessDeniedException { if (withdraw.doubleValue() > 10000) { boolean isManager = context.isCallerInRole("Manager"); if (!isManager) { // Only Managers can withdraw more than 10k. throw new AccessDeniedException(); } } balance = balance - withdraw.doubleValue(); } ... } The EJBContext contains some methods that were used in EJB 1.0 but were deprecated in EJB 1.1 and have been abandoned in EJB 2.0. Support for these deprecated methods is optional for EJB 1.1 containers, which can host EJB 1.0 beans. EJB containers that do not support the deprecated security methods will throw a RuntimeException. The deprecated security methods are based on EJB 1.0's use of the Identity object instead of the Principal object. The semantics of the deprecated methods are basically the same but, because Identity is an abstract class, it has proven to be too difficult to use. The getEnvironment() method has been replaced by the JNDI environment naming context, which is discussed later in this book. Support for the deprecated getEnvironment() method in EJB 1.1 is discussed in detail in Chapter 12. The transactional methods—getUserTransaction(), setRollbackOnly(), and getRollbackOnly()—are described in detail in Chapter 14. The material on the EJBContext covered in this section applies equally well to session and message-driven beans. There are some exceptions, however, and these differences are covered in Chapter 12 and Chapter 13. 11.4.2 JNDI ENCStarting with EJB 1.1, the bean-container contract for entity and stateful beans was expanded beyond the EJBContext using the Java Naming and Directory Interface ( JNDI). A special JNDI name space, which is referred to as the environment naming context (ENC), was added to allow any enterprise bean to access environment entries, other beans, and resources (such as JDBC DataSource objects) specific to that enterprise bean. The JNDI ENC continues to be an extremely important part of the bean-container contract in EJB 2.0. Although we used the JNDI ENC to access JDBC in the bean-managed persistence chapter (Chapter 10), it's not specific to entity beans. The JNDI ENC is used by session, entity, and message-driven beans alike. To avoid unnecessary duplication, a detailed discussion of this important facility is left for Chapter 12. What you learn about using the JNDI ENC in Chapter 12 applies equally well to session, entity, and message-driven beans. 11.5 The Life Cycle of an Entity BeanTo understand how to best develop entity beans, it is important to understand how the container manages them. The EJB specification defines just about every major event in an entity bean's life, from the time it is instantiated to the time it is garbage collected. This is called the life cycle, and it provides the bean developer and EJB vendors with all the information they need to develop beans and EJB servers that adhere to a consistent protocol. To understand the life cycle, we will follow an entity instance through several life-cycle events and describe how the container interacts with the entity bean during these events. Figure 11-2 illustrates the life cycle of an entity instance. Figure 11-2. Entity bean life cycle
This section identifies the points at which the container calls each of the methods described in the EntityBean interface as well as the find methods and, in EJB 2.0, the select and home methods. Bean instances must implement the EntityBean interface, which means that invocations of the callback methods are invocations on the bean instance itself. At each stage of the entity bean's life cycle, the bean container provides varying levels of access. For example, the EntityContext.getPrimary() method will not work if it is invoked in the ejbCreate() method, but it does work when called in the ejbPostCreate() method. Other EJBContext methods have similar restrictions, as does the JNDI ENC. While this section touches on the accessibility of these methods, a table called "Allowed operations" that details what is available in each bean class method (ejbCreate(), ejbActivate(), ejbLoad(), etc.) can be found in Appendix B. 11.5.1 Does Not ExistThe entity bean begins life as a collection of files. Included in that collection are the bean's deployment descriptor, component interfaces, and all the supporting classes generated at deployment time. At this stage, no instance of the bean exists. 11.5.2 The Pooled StateWhen the EJB server is started, it reads the EJB's files and instantiates several instances of the entity bean's bean class, which it places in a pool. The instances are created by calling the Class.newInstance() method on the bean class. The newInstance() method creates an instance using the default constructor, which has no arguments.[2] This means that the persistence fields of the bean instances are set at their default values; the instances themselves do not represent any data in the database. Immediately following the creation of an instance, and just before it is placed in the pool, the container assigns the instance its EntityContext. The EntityContext is assigned by calling the setEntityContext() method of the EntityBean interface, which is implemented by the bean class. After the instance has been assigned its context, it is entered into the instance pool. In the instance pool, the bean instance is available to the container as a candidate for servicing client requests. Until it is requested, however, the bean instance remains inactive unless it is used to service a query method (i.e., find or select method) or ejbHome() request. Bean instances in the Pooled state typically are used to service query and ejbHome() requests, which makes perfectly good sense because they aren't busy and these methods don't rely on the bean instance's state. All instances in the Pooled state are equivalent. None of the instances are assigned to an EJB object, and none of them has meaningful state. 11.5.3 The Ready StateWhen a bean instance is in the Ready state, it can accept client requests. A bean instance moves to the Ready state when the container assigns it to an EJB object. This occurs under two circumstances: when a new entity bean is being created, or when the container is activating an entity. 11.5.3.1 Transitioning from the Pooled state to the Ready state via creationWhen a client application invokes a create() method on an EJB home, several operations must take place before the EJB container can return a remote or local reference (EJB object) to the client. First, an EJB object must be created on the EJB server.[3] Once the EJB object is created, a entity bean instance is taken from the instance pool and assigned to the EJB object. Next, the create() method, invoked by the client, is delegated to its corresponding ejbCreate() method on the bean instance. After the ejbCreate() method completes, a primary key is created. When the ejbCreate() method is done, the ejbPostCreate() method on the entity bean instance is called. Finally, after the successful completion of the ejbPostCreate() method, the home is allowed to return a remote or local reference—an EJB object—to the client. The bean instance and EJB object are now ready to service method requests from the client. This is one way that the bean instance can move from the Pooled state to the Ready state. 11.5.3.2 Transitioning from the Pooled state to the Ready state via a query methodWhen a query method is executed, each EJB object that is found as a result of the query will be realized by transitioning an instance from the Pooled state to the Ready state. When an entity bean is found, it is assigned to an EJB object and its EJB object reference is returned to the client. A found bean follows the same protocol as a passivated bean; it is activated when the client invokes a business method, and will move into the Ready state through activation, as described in the next section. In many cases (depending on the EJB vendor), found entity beans don't actually migrate into the Ready state until they are accessed by the client. So, for example, if a find method returns a collection of entity beans, the entity beans may not be activated until they are obtained from the collection or accessed directly by the client. Resources are saved by activating entity beans lazily (as needed). 11.5.3.3 Transitioning from the Pooled state to the Ready state via activationThe activation process can also move an entity bean instance from the Pooled state to the Ready state. Activation facilitates resource management by allowing a few bean instances to service many EJB objects. Activation was explained in Chapter 3, but we will revisit the process here as it relates to the entity bean instance's life cycle. Activation presumes that the entity bean has previously been passivated. More is said about this state transition later; for now, suffice it to say that when a bean instance is passivated, it frees any resources that it does not need and leaves the EJB object for the instance pool. When the bean instance returns to the pool, the EJB object is left without an instance to which to delegate client requests. The EJB object maintains its stub connection on the client, so as far as the client is concerned, the entity bean hasn't changed. When the client invokes a business method on the EJB object, the EJB object must obtain a new bean instance. This is accomplished by activating a bean instance. When a bean instance is activated, it leaves the instance pool (the Pooled state) to be assigned to an EJB object. Once assigned to the proper EJB object, the ejbActivate() method is called—the instance's EntityContext can now provide information specific to the EJB object, but it cannot provide security or transactional information. The ejbActivate() callback method can be used in the bean instance to reobtain resources or perform any other necessary work before servicing the client. When an entity bean instance is activated, nonpersistent instance fields of the bean instance may contain arbitrary (dirty) values and so must be reinitialized in the ejbActivate() method. In container-managed persistence, container-managed fields are automatically synchronized with the database after ejbActivate() is invoked and before a business method can be serviced by the bean instance. The order in which these things happen in CMP entity beans is:
In bean-managed persistence, persistence fields are synchronized by the ejbLoad() method after ejbActivate() has been called and before a business method can be invoked. Here is the order of operations in bean-managed persistence:
11.5.3.4 Transitioning from the Ready state to the Pooled state via passivationA bean can move from the Ready state to the Pooled state via passivation, which is the process of disassociating a bean instance from an EJB object when it is not busy. After a bean instance has been assigned to an EJB object, the EJB container can passivate the instance at any time, provided that the instance is not currently executing a method. As part of the passivation process, the ejbPassivate() method is invoked on the bean instance. This callback method can be used by the instance to release any resources or perform other processing prior to leaving the EJB object. When ejbPassivate() has completed, the bean instance is disassociated from the EJB object server and returned to the instance pool. The bean instance is now back in the Pooled state. A bean-managed entity instance should not try to save its state to the database in the ejbPassivate() method; this activity is reserved for the ejbStore() method. The container will invoke ejbStore() to synchronize the bean instance's state with the database prior to passivating the bean. The most fundamental thing to remember is that, for entity beans, passivation is simply a notification that the instance is about to be disassociated from the EJB object. Unlike stateful session beans, an entity bean instance's fields are not serialized and held with the EJB object when the bean is passivated. Whatever values were held in the instance's nonpersistent fields when the entity bean was assigned to the EJB object will be carried with it to its next assignment. 11.5.3.5 Transitioning from the Ready state to the Pooled state via removalA bean instance also moves from the Ready state to the Pooled state when it is removed. This occurs when the client application invokes one of the remove methods on the bean's EJB object or EJB home. With entity beans, invoking a remove method deletes the entity's data from the database. Once the entity's data has been deleted from the database, it is no longer a valid entity. Once the ejbRemove() method has finished, the bean instance is moved back to the instance pool and out of the Ready state. It is important that the ejbRemove() method release any resources that would normally be released by ejbPassivate(), which is not called when a bean is removed. This can be done, if need be, by invoking the ejbPassivate() method within the ejbRemove() method body. In bean-managed persistence, the ejbRemove() method is implemented by the entity bean developer and includes code to delete the entity bean's data from the database. The EJB container will invoke the ejbRemove() method in response to a client's invocation of the remove() method on one of the component interfaces. In container-managed persistence, the ejbRemove() method notifies the entity bean instance that its data is about to be removed from the database. Immediately following the ejbRemove() call, the container deletes the entity bean's data. In EJB 2.0 CMP the container also cleans up the entity bean's relationships with other entity beans in the database. If a cascade delete is specified, it removes each entity bean in the cascade delete relationships. This involves activating each entity bean and calling its ejbActivate() methods, loading each entity bean's state by calling its ejbLoad() method, calling the ejbRemove() on all of the entity beans in the cascade-delete relationship, and then deleting their data. This process can continue in a chain until all the cascade-delete operations of all the relationships have completed. 11.5.4 Life in the Ready StateA bean is in the Ready state when it is associated with an EJB object and is ready to service requests from the client. When the client invokes a business method, like Ship.getName(), on the bean's remote or local reference (EJB object), the method invocation is received by the EJB server and delegated to the bean instance. The instance performs the method and returns the results. As long as the bean instance is in the Ready state, it can service all the business methods invoked by the client. Business methods can be called zero or more times, in any order. In addition to servicing business methods, an entity bean in the Ready state can execute select methods, which are called by the bean instance while servicing a business method. The ejbLoad() and ejbStore() methods, which synchronize the bean instance's state with the database, can be called only when the bean is in the Ready state. These methods can be called in any order, depending on the vendor's implementation. Some vendors call ejbLoad() before every method invocation and ejbStore() after every method invocation, depending on the transactional context. Other vendors call these methods less frequently. In bean-managed persistence, the ejbLoad() method should always use the EntityContext.getPrimaryKey() method to obtain data from the database and should not trust any primary key or other data that the bean has stored in its fields. (This is how we implemented it in the bean-managed version of the Ship bean in Chapter 10.) It should be assumed, however, that the state of the bean is valid when calling the ejbStore() method. In container-managed persistence, the ejbLoad() method is always called immediately following the synchronization of the bean's container-managed fields with the database—in other words, right after the container updates the state of the bean instance with data from the database. This provides an opportunity to perform any calculations or reformat data before the instance can service business-method invocations from the client. The ejbStore() method is called just before the database is synchronized with the state of the bean instance—just before the container writes the container-managed fields to the database. This provides the CMP entity bean instance with an opportunity to change the data in the container-managed fields prior to their persistence to the database. In bean-managed persistence, the ejbLoad() and ejbStore() methods are called when the container deems it appropriate to synchronize the bean's state with the database. These are the only callback methods that should be used to synchronize the bean's state with the database. Do not use ejbActivate(), ejbPassivate(), setEntityContext(), or unsetEntityContext() to access the database for the purpose of synchronization. You should use the ejbCreate() and ejbRemove() methods, however, to insert and delete (respectively) the entity's data into and from the database. 11.5.5 End of the Life CycleA bean instance's life cycle ends when the container decides to remove it from the pool and allow it to be garbage collected. This happens under a few different circumstances. If the container decides to reduce the number of instances in the pool—usually to conserve resources—it releases one or more bean instances and allows them to be garbage collected. The ability to adjust the size of the instance pool allows the EJB server to manage its resources (the number of threads, available memory, etc.) so that it can achieve the highest possible performance. When an EJB server is shut down, most containers release all the bean instances so that they can be safely garbage collected. Some containers may also decide to release any instances that are behaving unfavorably or that have suffered from some kind of unrecoverable error that makes them unstable. For example, any time an entity bean instance throws a type of RuntimeException from any of its methods, the EJB container will evict that instance from memory and replace it with a stable instance from the instance pool. When an entity bean instance leaves the instance pool to be garbage collected, the unsetEntityContext() method is invoked by the container to alert the bean instance that it is about be destroyed. This callback method lets the bean instance release any resources it maintains before being garbage collected. Once the bean's unsetEntityContext() method has been called, it is garbage collected. The bean instance's finalize() method may or may not be invoked following the unsetEntityContext() method. A bean should not rely on its finalize() method, since each vendor handles evicting instances differently.
| ||||||||||||||||||
|