Although CMP and BMP entities are programmed differently, their relationships to the container system at runtime is 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 CMP and BMP are important, they will be noted.
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 and Chapter 9, 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 class that
declares several instance variables that map to more than one
persistence field in the bean class.
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
persistent field is of type
java.lang.Integer
, one of the single-field primary
key types. The term “persistent
field” means a container-managed persistent field in
CMP entities or a instance field in an BMP entity that maps to the
beans state in the database. In 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. For the BMP ShipBean
class defined in Chapter 9, the
Integer
primary key maps to the
id
instance field:
public class ShipBean implements javax.ejb.EntityBean {
public Integer id;
public String name;
...
}
In CMP entities, you identify the CMP field that will serve as the
single-field primary key using the
<primkey-field>
element in the deployment
descriptor. In addition, the
<prim-key-class>
element specifies the type
of object used for the primary key class. The CMP Ship EJB discussed
in Chapter 7 uses both of these elements when
defining the id
CMP 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>
In BMP entities you do not specify a
<primkey-field>
, because primary keys are
created by the bean code, not the container. However, you are
required to identify the <prim-key-class>
with BMP entities as shown in the following listing.
<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>Bean</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> </entity>
Although primary keys can be primitive wrappers
(Integer
, Double
,
Long
, etc.), they cannot be
primitive types (int
,
double
, long
, etc.) because the
semantics of the EJB programming model require the use of
Object
type primary keys. For example, the
EJBObject.getPrimaryKey( )
method returns an
Object
type, thus forcing primary keys to be
Object
s. 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.
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
persistent field to the Ship EJB for
this example.) In this case, the name
and
registration
persistent 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. 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.
With CMP entities, 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.
With BMP entities, the instance fields of the primary key class are not required to map exactly to corresponding persistent fields in the bean class. The bean class is directly responsible for creating and managing the instance fields of the primary key, not the container. In most cases, however, the instance fields in the primary key will map to persistent fields in the bean class.
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 EJB is created in CMP, 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 on the bean class of both BMP and CMP entities 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 CMP ShipBean
class we developed for 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) {
}
...
The deployment descriptor for CMP entities is required to define
<cmp-field>
entries that match the instance fields
of the compound primary key, but it must not
define a <primkey-field>
element. The
<primkey-field>
element is only used with single-field
primary keys. The deployment descriptor for CMP entities
must define a
<prim-key-class>
for compound primary keys,
however.
<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>com.titan.ShipPK</prim-key-class> <reentrant>False</reentrant> <cmp-field><field-name>name</field-name></cmp-field> <cmp-field><filed-name>registration</field-name></cmp-field> <cmp-field><field-name>tonnage</field-name></cmp-field> </entity>
Here is how the ShipPK
primary key class might be
used in the BMP ShipBean
class we developed for in
Chapter 9:
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 deployment descriptor for BMP entities is always required to
define the
<prim-key-class>
for both single-field and compound primary keys as shown in the
following listing:
<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>Bean</persistence-type> <prim-key-class>com.titan.ShipPK</prim-key-class> </entity>
The ejbCreate( )
method of both the BMP and CMP
entities now declares 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
(CMP only).
In 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 in CMP 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 for both CMP and BMP
entities is 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.
CMP requires 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
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 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 CMP bean class
and its interfaces use the
Object
type to
identify the primary key. The Ship EJB developed in Chapter 7 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.