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.

The Primary Key

A 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.

Single-Field Primary Keys

The 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.

Compound Primary Keys

A 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) && 
            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.

Undefined Primary Keys

Undefined 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset