When you deploy an EJB 1.1 CMP entity bean, you must identify which fields in the entity will be managed by the container and how they map to the database. The container then automatically generates the logic necessary to save the bean instance’s state.
Fields that are mapped to the database are called container-managed fields—EJB 1.1 doesn’t support relationship fields, as EJB 2.0 does. Container-managed fields can be any Java primitive type or serializable object. Most beans use Java primitive types when persisting to a relational database, since it’s easier to map Java primitives to relational data types.
EJB 1.1 also allows references to other beans to be container-managed
fields. For this to work, the EJB vendor must support converting bean
references (remote or home interface types) from remote references to
something that can be persisted in the database and converted back to
a remote reference automatically. Vendors will normally convert
remote references to primary keys, Handle
or
HomeHandle
objects, or some other proprietary
pointer type that can be used to preserve the bean reference in the
database. The container will manage this conversion from remote
reference to a persistent pointer and back automatically. This
feature was abandoned in EJB 2.0 CMP in favor of container-managed
relationship fields.
The advantage of container-managed persistence is that the bean can be defined independently of the database used to store its state. Container-managed beans can take advantage of both relational and object-oriented databases. The bean state is defined independently, which makes the bean reusable and more flexible.
The disadvantage of container-managed beans is that they require sophisticated mapping tools to define how the beans’ fields map to the database. In some cases, this is a simple matter of mapping each field in the bean instance to a column in the database or of serializing the bean to a file. In other cases, it is more difficult. The state of some beans, for example, may be defined in terms of a complex relational database join or mapped to some kind of legacy system such as CICS or IMS.
In this chapter we will create a new container-managed entity bean, the Ship EJB, which we will examine in detail. A Ship EJB is also used in both Chapter 7, when discussing complex relationships in EJB 2.0, and Chapter 10, when discussing bean-managed persistence. When you are done with this chapter you may want compare the Ship EJB developed here with the ones created in Chapter 7 and Chapter 10.
Let’s start by thinking about what we’re trying to do. An
enormous amount of data would go into a complete description of a
ship, but for our purposes we will limit the scope of the data to a
small set of information. For now, we can say that a ship has the
following characteristics or attributes: its name, passenger
capacity, and tonnage (i.e., size). The Ship EJB will encapsulate
this data, and we’ll need to create a SHIP
table in our database to hold the data.
Here is the definition for the SHIP
table
expressed in
standard SQL:
CREATE TABLE SHIP (ID INT PRIMARY KEY NOT NULL, NAME CHAR(30), CAPACITY INT, TONNAGE DECIMAL(8,2))
When defining any bean, we start by coding the remote interfaces. This focuses our attention on the most important aspect of the bean: its business purpose. Once we have defined the interfaces, we can start working on the actual bean definition.
We will need a remote interface for the Ship EJB. This interface
defines the business methods clients will use to interact with the
bean. When defining the remote interface, we will take into account
all the different areas in Titan’s system that may want to use
the ship concept. Here is the remote interface,
ShipRemote
, for the Ship EJB:
package com.titan.ship; import javax.ejb.EJBObject; import java.rmi.RemoteException; public interface ShipRemote extends javax.ejb.EJBObject { public String getName() throws RemoteException; public void setName(String name) throws RemoteException; public void setCapacity(int cap) throws RemoteException; public int getCapacity() throws RemoteException; public double getTonnage() throws RemoteException; public void setTonnage(double tons) throws RemoteException; }
The remote home interface of any entity bean is used to create, locate, and remove objects from EJB systems. Each entity bean type has its own home interface. The home interface defines two basic kinds of methods: zero or more create methods and one or more find methods.[35] The create methods act like remote constructors and define how new Ship EJBs are created. (In our home interface, we provide only a single create method.) The find method is used to locate a specific Ship or Ships.
The following code contains the complete definition of the
ShipHomeRemote
interface.
package com.titan.ship; import javax.ejb.EJBHome; import javax.ejb.CreateException; import javax.ejb.FinderException; import java.rmi.RemoteException; import java.util.Enumeration; public interface ShipHomeRemote extends javax.ejb.EJBHome { public ShipRemote create(Integer id, String name, int capacity, double tonnage) throws RemoteException,CreateException; public ShipRemote create(Integer id, String name) throws RemoteException,CreateException; public ShipRemote findByPrimaryKey(Integer primaryKey) throws FinderException, RemoteException; public Enumeration findByCapacity(int capacity) throws FinderException, RemoteException; }
Enterprise JavaBeans specifies that create methods in the home
interface must throw the
javax.ejb.CreateException
. In the case of
container-managed persistence, the container needs a common exception
for communicating problems experienced during the create process.
EJB 1.1 CMP supports only find methods, not EJB 2.0’s select methods. In addition, only the remote home interface supports find methods; EJB 1.1 entity beans do not support local component interfaces.
With EJB 1.1 container-managed persistence, implementations of the
find methods are generated automatically at deployment time.
Different EJB-container vendors employ different strategies for
defining how the find methods work. Regardless of the implementation,
when you deploy the bean, you’ll need to do some work to define
the rules of the find method. findByPrimaryKey()
is a standard method that all home interfaces for entity beans must
support. This method locates beans based on the attributes of the
primary key. In the case of the Ship EJB, the primary key is the
Integer
class, which maps to the
id
field of the ShipBean
. With
relational databases, the primary key attributes usually map to a
primary key in a table. In the
ShipBean
class, for
example, the id
attribute maps to the
ID
primary key column in the
SHIP
table. In an object-oriented database, the
primary key’s attributes might point to some other unique
identifier.
EJB 1.1 allows you to specify other find methods in the home
interface, in addition to findByPrimaryKey()
. All
find methods must have names that match the pattern
find<
SUFFIX
>()
.
So, for example, if we were to include a find method based on the
Ship EJB’s capacity, it might be called
findByCapacity(int
capacity)
.
In container-managed persistence, any find method included in the
home interface must be explained to the container. In other words,
the deployer needs to define how the find method should work in terms
the container understands. This is done at deployment time, using the
vendor’s deployment tools and syntax specific to the vendor.
Find methods return either the remote interface type appropriate for
that bean, or an instance of the
java.util.Enumeration
or
java.util.Collection
type. Unlike EJB 2.0 CMP, EJB 1.1 CMP
doesn’t support
java.util.Set
as a return type for find methods.
Specifying a remote interface type indicates that the method locates
only one bean. The findByPrimaryKey()
method
obviously returns a single remote reference because there is a
one-to-one relationship between a primary key’s value and an
entity. The findByCapacity(int
capacity)
method, however, could return several
remote references, one for every ship that has a capacity equal to
the parameter capacity. To be able to return more than one remote
reference, the find method must return either the
Enumeration
or Collection
type.
Enterprise JavaBeans specifies that any find method used in a home
interface must throw the
javax.ejb.FinderException
. Find methods that
return a single remote reference throw a
FinderException
if an application error occurs and
a javax.ejb.ObjectNotFoundException
if a matching
bean cannot be found. The ObjectNotFoundException
is a subtype of FinderException
and is thrown
only by find methods that return single remote
references. Find methods that return an
Enumeration
or Collection
type
(multi-entity finders) return an empty collection (not a
null
reference) if no matching beans can be found
and throw a FinderException
if an application
error occurs.
How find methods are mapped to the database for container-managed persistence is not defined in the EJB 1.1 specification—it is vendor specific. Consult the documentation provided by your EJB vendor to determine how find methods are defined at deployment time. Unlike EJB 2.0 CMP, there is no standard query language for expressing the behavior of find methods at runtime.
A primary key is an object that uniquely identifies an entity bean
according to the bean type, home interface, and container context
from which it is used. In container-managed persistence, a primary
key can be a serializable object defined specifically for the bean by
the bean developer, or its definition can be deferred until
deployment. The primary key defines attributes that can be used to
locate a specific bean in the database. In this case we need only one
attribute, id
, but it is possible for a primary
key to have several attributes, all of which uniquely identify a
bean’s data. We will examine primary keys in detail in Chapter 11; for now, we’ll specify that the Ship
EJB use a simple single-value primary key of type
java.lang.Integer
.
No bean is complete without its
implementation class.
Now that we have defined the Ship EJB’s remote interfaces and
primary key, we are ready to define the ShipBean
itself. The ShipBean
will reside on the EJB
server. When a client application or bean invokes a business method
on the Ship EJB’s remote interface, that method invocation will
be received by the EJB object, which will then delegate it to the
ShipBean
instance.
When developing any bean, we have to use the bean’s remote
interfaces as a guide. Business methods defined in the remote
interface must be duplicated in the bean class. In container-managed
beans, the create methods of the home interface must also have
matching methods in the bean class, according to the EJB 1.1
specification. Finally, callback methods defined by the
javax.ejb.EntityBean
interface must be
implemented. Here is the code for the ShipBean
class:
package com.titan.ship; import javax.ejb.EntityContext; public class ShipBean implements javax.ejb.EntityBean { public Integer id; public String name; public int capacity; public double tonnage; public EntityContext context; public Integer ejbCreate(Integer id, String name, int capacity, double tonnage) { this.id = id; this.name = name; this.capacity = capacity; this.tonnage = tonnage; return null; } public Integer ejbCreate(Integer id, String name) { this.id = id; this.name = name; capacity = 0; tonnage = 0; return null; } public void ejbPostCreate(Integer id, String name, int capacity, double tonnage) { Integer pk = (Integer)context.getPrimaryKey(); // Do something useful with the primary key. } public void ejbPostCreate(int id, String name) { ShipRemote myself = (ShipRemote)context.getEJBObject(); // Do something useful with the EJBObject reference. } public void setEntityContext(EntityContext ctx) { context = ctx; } public void unsetEntityContext() { context = null; } public void ejbActivate() {} public void ejbPassivate() {} public void ejbLoad() {} public void ejbStore() {} public void ejbRemove() {} public String getName() { return name; } public void setName(String n) { name = n; } public void setCapacity(int cap) { capacity = cap; } public int getCapacity() { return capacity; } public double getTonnage() { return tonnage; } public void setTonnage(double tons) { tonnage = tons; } }
The Ship EJB defines four persistence fields: id
,
name
, capacity
, and
tonnage
. These fields represent the persistence
state of the Ship EJB; they are the state that defines a unique Ship
entity in the database. The Ship EJB also defines another field,
context
, which holds the bean’s
EntityContext
. We’ll have more to say about
this field later.
The set and get methods are the business methods we defined for the
Ship EJB; both the remote interface and the bean class must support
them. This means that the signatures of these methods must be exactly
the same, except for the
javax.ejb.RemoteException
. The bean class’s
business methods aren’t required to throw the
RemoteException
. This makes sense because these
methods aren’t actually invoked remotely—they’re
invoked by the EJB object. If a communication problem occurs, the
container will throw the RemoteException
for the
bean automatically.
Because it is an entity bean, the Ship EJB 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 RemoteException; public abstract void ejbPassivate() throws RemoteException; public abstract void ejbLoad() throws RemoteException; public abstract void ejbStore() throws RemoteException; public abstract void ejbRemove() throws RemoteException; public abstract void setEntityContext(EntityContext ctx) throws RemoteException; public abstract void unsetEntityContext() throws RemoteException; }
Each callback method is called at a specific time during the life
cycle of a ShipBean
instance. In many cases,
container-managed beans (like the Ship EJB) don’t need to do
anything when a callback method is invoked. The persistence of
container-managed beans is managed automatically; much of the
resources and logic that might be managed by these methods is already
handled by the container.
This version of the Ship EJB has empty implementations for its
callback methods. It is important to note, however, that even a
container-managed bean can take advantage of these callback methods
if needed; we just don’t need them in our
ShipBean
class at this time. The callback methods
are examined in detail in Chapter 11. You should
read that chapter to learn more about the callback methods and when
they are invoked.
When a create()
method is invoked on the home
interface, the EJB home delegates it to the bean instance in the same
way that business methods on the remote interface are handled. This
means we need an ejbCreate()
method in the bean
class that corresponds to each create()
method in
the home interface.
The ejbCreate()
method returns a
null
value of type Integer
for
the bean’s primary key. The return value of the
ejbCreate()
method for a container-managed bean is
actually ignored by the container.
EJB 1.1 changed the
ejbCreate()
method’s return value from void
, which was
the return type in EJB 1.0, to the primary key type to facilitate
subclassing—this makes it easier for a bean-managed bean to
extend a container-managed bean. In EJB 1.0, because the return type
was void
, this was not possible: Java won’t
allow you to overload methods with different return values. By
changing this definition so that a bean-managed bean can extend a
container-managed bean, the EJB 1.1 specification allows vendors to
support container-managed persistence by extending the
container-managed bean with a generated bean-managed bean—a
fairly simple solution to a difficult problem. Bean developers can
also take advantage of inheritance to change an existing CMP bean
into a BMP bean, which may be necessary to overcome difficult
persistence problems.
For every create()
method defined in the entity
bean’s home interface, there must be a corresponding
ejbPostCreate()
method in the bean instance class.
In other words, the ejbCreate()
and
ejbPostCreate()
methods occur in pairs with
matching signatures; there must be one pair for each
create()
method defined in the home interface.
In a container-managed bean, the ejbCreate()
method is called just prior to writing the bean’s
container-managed fields to the database. Values passed in to the
ejbCreate()
method should be used to initialize
the fields of the bean instance. Once the
ejbCreate()
method completes, a new record, based
on the container-managed fields, is written to the database.
The bean developer must ensure that the
ejbCreate()
method sets the persistence fields
that correspond to the fields of the primary key. When a compound
primary key is defined for a container-managed bean, it must define
fields that match one or more of the container-managed (persistence)
fields in the bean class. The fields must match exactly with regard
to type and name. At runtime, the container will assume that fields
in the primary key match some or all of the fields in the bean class.
When a new bean is created, the container will use the
container-managed fields in the bean class to instantiate and
populate a primary key for the bean automatically.
Once the bean’s state has been populated and its
EntityContext
has been established, an
ejbPostCreate()
method is invoked. This method
gives the bean an opportunity to perform any postprocessing prior to
servicing client requests. The bean identity isn’t available to
the bean during the call to ejbCreate()
, but it is
available in the ejbPostCreate()
method. This
means that the bean can access its own primary key and EJB object,
which can be useful for initializing the bean instance prior to
servicing business-method invocations. You can use the
ejbPostCreate()
method to perform any additional
initialization. Each ejbPostCreate()
method must
have the same parameters as its corresponding
ejbCreate()
method. The
ejbPostCreate()
method returns
void
.
For more information about the ejbCreate()
and
ejbPostCreate()
methods and how they relate to the
life cycle of entity beans, see Chapter 11.
The process of ensuring that the database record and the entity bean
instance are equivalent is called
synchronization. In container-managed
persistence, the bean’s container-managed fields are
automatically synchronized with the database. In most cases we will
not need the ejbLoad()
and
ejbStore()
methods, because persistence in
container-managed beans is uncomplicated. These methods are covered
in more detail in Chapter 11.
With a complete definition of the Ship EJB, including the remote
interface and the home interface, we are ready to create a deployment
descriptor. The following code shows the bean’s XML deployment
descriptor. The <cmp-field>
element is
particularly important. These elements list the fields that are
managed by the container; they have the same meaning as they do in
EJB 2.0 container-managed persistence:
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd"> <ejb-jar> <enterprise-beans> <entity> <description> This bean represents a cruise ship. </description> <ejb-name>ShipEJB</ejb-name> <home>com.titan.ship.ShipHomeRemote</home> <remote>com.titan.ship.ShipRemote</remote> <ejb-class>com.titan.ship.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>capacity</field-name></cmp-field>
<cmp-field><field-name>tonnage</field-name></cmp-field>
<primkey-field>id</primkey-field> </entity> </enterprise-beans> <assembly-descriptor> <security-role> <description> This role represents everyone who is allowed full access to the Ship EJB. </description> <role-name>everyone</role-name> </security-role> <method-permission> <role-name>everyone</role-name> <method> <ejb-name>ShipEJB</ejb-name> <method-name>*</method-name> </method> </method-permission> <container-transaction> <method> <ejb-name>ShipEJB</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
The <cmp-field>
elements list all the
container-managed fields in the entity bean class. These are the
fields that will be persisted in the database and are managed by
the
container
at runtime.
Please refer to Workbook Exercise 9.1, A CMP 1.1 Entity Bean. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.