All 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.
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.
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.
To 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.
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).[38] 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 SHIP
table.
In bean-managed persistence, the bean class itself reads the fields and performs a database insert to add the bean’s data to the database. This would take place in steps 5 and 6.
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).
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 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 methodspublic 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.
In 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.
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.
Even in EJB containers that do not pool entity bean instances, the
value of ejbActivate()
and
ejbPassivate()
is questionable. It’s
possible that an EJB container may choose to evict instances from
memory between client invocations and create a new instance for each
new transaction. While this may appear to hurt performance,
it’s a reasonable design, provided that the container
system’s Java Virtual Machine has an extremely efficient
garbage collection and memory allocation strategy. Hotspot is an
example of a JVM that has made some important advances in this area.
Even in this case, however, ejbActivate()
and
ejbPassivate()
provide little value because the
setEntityContext()
and
unsetEntityContext()
methods can accomplish the
same thing.
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.
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.
[38] The specification does
not actually require that the record be inserted into the database
immediately after the ejbCreate()
method is called
(step 6). As an alternative, the record insert may be deferred until
after the ejbPostCreate()
method executes or even
until the end of the transaction.