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.
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.
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
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
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; publicShipPK
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 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.