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, RemoveException; 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 9, 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 2.0 and EJB 2.1
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 either; 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 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 CMP 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 interfaces (see
Chapter 7 and Chapter 9),
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 invoked on an EJB home to the
proper ejbCreate( )
method in the bean instance.
In addition, 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)
.
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 CMP 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
ejbCreate( )
method has executed, the
ejbPostCreate( )
method is invoked. This method
gives the bean an opportunity to perform any postprocessing prior to
servicing client requests. In CMP entity beans,
ejbPostCreate( )
is used to manipulate
container-managed relationship (CMR) 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 ejbCreate( )
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.
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
.
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, in CMP,
it is possible that one of the parameters passed is not assigned to a
CMP field. In this case, you would need to duplicate the parameters
of the ejbCreate( )
method to have that
information available in the ejbPostCreate( )
method. CMR fields are the primary reason for utilizing the
ejbPostCreate( )
method in 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 10-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 (CMP) or bean class (BMP) can insert a record into the
database. In the case of the ShipBean
, the minimal
information required to add a new ship to the system is the
ship’s name
and unique
id
. These persistent fields are set during the
ejbCreate( )
method invocation (step 4).
In container-managed persistence, the container uses two of the
bean’s CMP fields (id
and
name
), to insert a record into the database. 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 (step 5), it will automatically insert a new record into the
database using those fields (step 6).[26] 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 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 methods public 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 9.
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 done in
practice.
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, 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 deletes the entity bean’s data from the database.
[26] 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.