To understand how to best develop entity beans, it is important to understand how the container manages them. The EJB specification defines just about every major event in an entity bean’s life, from the time it is instantiated to the time it is garbage collected. This is called the life cycle, and it provides the bean developer and EJB vendors with all the information they need to develop beans and EJB servers that adhere to a consistent protocol. To understand the life cycle, we will follow an entity instance through several life-cycle events and describe how the container interacts with the entity bean during these events. Figure 10-2 illustrates the life cycle of an entity instance.
This section identifies the points at which the container calls each
of the methods described in the EntityBean
interface as well as the find methods and the select and home
methods. Bean instances must implement the
EntityBean
interface, which means that invocations
of the callback methods are invocations on the bean instance itself.
At each stage of the entity bean’s life cycle, the
bean container provides varying levels of access. For example, the
EntityContext.getPrimaryKey( )
method will not
work if it is invoked in the ejbCreate( )
method,
but it does work when called in the ejbPostCreate( )
method. Other EJBContext
methods have
similar restrictions, as does the JNDI ENC.
The entity bean begins life as a collection of files. Included in that collection are the bean’s deployment descriptor, component interfaces, and all the supporting classes generated at deployment time. At this stage, no instance of the bean exists.
When the EJB server is started, it reads
the EJB’s files and instantiates several instances
of the entity bean’s bean class, which it places in
a pool. The instances are created by calling the
Class.newInstance( )
method on the bean class. The
newInstance( )
method creates an instance using
the default constructor, which has no arguments.[27] This means that the persistence fields of the bean
instances are set at their default values; the instances themselves
do not represent any data in the database.
Immediately following the creation of an instance, and just before it
is placed in the pool, the container assigns the instance its
EntityContext
. The
EntityContext
is assigned by calling the
setEntityContext( )
method of the EntityBean
interface, which is
implemented by the bean class. After the instance has been assigned
its context, it is entered into the instance pool.
In the instance pool, the bean instance is available to the container
as a candidate for servicing client requests. Until it is requested,
however, the bean instance remains inactive unless it is used to
service a query method (i.e., find or select method) or
ejbHome( )
request. Bean instances in the Pooled
state typically are used to service query and ejbHome( )
requests, which makes perfectly good sense because they
aren’t busy and these methods don’t
rely on the bean instance’s state. All instances in
the Pooled state are equivalent. None of the instances are assigned
to an EJB object, and none of them has meaningful state.
When a bean instance is in the Ready state, it can accept client requests. A bean instance moves to the Ready state when the container assigns it to an EJB object. This occurs under two circumstances: when a new entity bean is being created, or when the container is activating an entity.
When a
client application invokes a
create( )
method on an EJB home, several
operations must take place before the EJB container can return a
remote or local reference (EJB object) to the client. First, an EJB
object must be created on the EJB server.[28] Once the EJB object is
created, an entity bean instance is taken from the instance pool and
assigned to the EJB object. Next, the create( )
method, invoked by the client, is delegated to its corresponding
ejbCreate( )
method on the bean instance. After
the ejbCreate( )
method completes, a primary key is
created.
When the ejbCreate( )
method is done, the
ejbPostCreate( )
method on the entity bean
instance is called. Finally, after the successful completion of the
ejbPostCreate( )
method, the home is allowed to
return a remote or local reference—an EJB object—to the
client. The bean instance and EJB object are now ready to service
method requests from the client. This is one way that the bean
instance can move from the Pooled state to the Ready state.
When a query method is executed, each EJB object that is found as a result of the query will be realized by transitioning an instance from the Pooled state to the Ready state. When an entity bean is found, it is assigned to an EJB object and its EJB object reference is returned to the client. A found bean follows the same protocol as a passivated bean; it is activated when the client invokes a business method, and will move into the Ready state through activation, as described in the next section.
In many cases (depending on the EJB vendor), found entity beans don’t actually migrate into the Ready state until they are accessed by the client. So, for example, if a find method returns a collection of entity beans, the entity beans may not be activated until they are obtained from the collection or accessed directly by the client. Resources are saved by activating entity beans lazily (as needed).
The activation process can also move an entity bean instance from the Pooled state to the Ready state. Activation facilitates resource management by allowing a few bean instances to service many EJB objects. Activation was explained in Chapter 3, but we will revisit the process here as it relates to the entity bean instance’s life cycle. Activation presumes that the entity bean has previously been passivated . More is said about this state transition later; for now, suffice it to say that when a bean instance is passivated, it frees any resources that it does not need and leaves the EJB object for the instance pool. When the bean instance returns to the pool, the EJB object is left without an instance to which to delegate client requests. The EJB object maintains its stub connection on the client, so as far as the client is concerned, the entity bean hasn’t changed. When the client invokes a business method on the EJB object, the EJB object must obtain a bean instance. This is accomplished by activating a bean instance.
When a bean instance is activated, it leaves the instance pool (the
Pooled state) to be assigned to an EJB object. Once assigned to the
proper EJB object, the
ejbActivate( )
method
is called—the instance’s
EntityContext
can now provide information specific
to the EJB object, but it cannot provide security or transactional
information. The ejbActivate( )
callback method can be used in the
bean instance to reobtain resources or perform any other necessary
work before servicing the client.
When an entity bean instance is activated, nonpersistent instance
fields of the bean instance may contain arbitrary (dirty) values and
should be reinitialized in the ejbActivate( )
method.
In container-managed persistence, container-managed fields are
automatically synchronized with the database after
ejbActivate( )
is invoked and before a business
method can be serviced by the bean instance. The order in which these
things happen in CMP entity beans is:
ejbActivate( )
is invoked on the bean instance.
Persistence fields are synchronized automatically.
ejbLoad( )
notifies the bean that its persistence
fields have been synchronized.
Business methods are invoked as needed.
In bean-managed persistence, persistence fields are synchronized by
the ejbLoad( )
method after ejbActivate( )
has been called and before a business method can be
invoked. Here is the order of operations in bean-managed persistence:
ejbActivate( )
is invoked on the bean instance.
ejbLoad( )
is called to let the bean synchronize
its persistence fields.
Business methods are invoked as needed.
A bean can move from the Ready state to the Pooled state via
passivation, which is the process of disassociating a bean instance
from an EJB object when it is not busy. After a bean instance has
been assigned to an EJB object, the EJB container can passivate the
instance at any time, provided that the instance is not currently
executing a method. As part of the passivation process, the
ejbPassivate( )
method is invoked on the bean
instance. This callback method can be used by the instance to release
any resources or perform other processing prior to leaving the EJB
object. When ejbPassivate( )
has completed, the
bean instance is disassociated from the EJB object server and
returned to the instance pool. The bean instance is now back in the
Pooled state.
A
bean-managed entity instance should not try to save its state to the
database in the ejbPassivate( )
method; this
activity is reserved for the ejbStore( )
method.
The container will invoke ejbStore( )
to
synchronize the bean instance’s state with the
database prior to passivating the bean.
The most fundamental thing to remember is that, for entity beans, passivation is simply a notification that the instance is about to be disassociated from the EJB object. Unlike stateful session beans, an entity bean instance’s fields are not serialized and held with the EJB object when the bean is passivated. Whatever values were held in the instance’s nonpersistent fields when the entity bean was assigned to the EJB object will be carried with it to its next assignment.
A bean instance also moves from the Ready state to the Pooled state when it is removed. This occurs when the client application invokes one of the remove methods on the bean’s EJB object or EJB home. With entity beans, invoking a remove method deletes the entity’s data from the database. Once the entity’s data has been deleted from the database, it is no longer a valid entity.
Once the
ejbRemove( )
method
has finished, the bean instance is moved back to the instance pool
and out of the Ready state. It is important that the
ejbRemove( )
method release any resources that
would normally be released by
ejbPassivate( )
, which
is not called when a bean is removed. This can be done, if need be,
by invoking the ejbPassivate( )
method within the
ejbRemove( )
method body.
In bean-managed persistence, the ejbRemove( )
method is implemented by the entity bean developer and includes code
to delete the entity bean’s data from the database.
The EJB container will invoke the ejbRemove( )
method in response to a client’s invocation of the
remove( )
method on one of the component
interfaces.
In container-managed persistence, the ejbRemove( )
method notifies the entity bean instance that its data is about to be
removed from the database. Immediately following the
ejbRemove( )
call, the container deletes the
entity bean’s data.
In CMP the container also cleans up the entity
bean’s relationships with other entity beans in the
database. If a cascade delete is specified, it removes each entity
bean in the cascade delete relationships. This involves activating
each entity bean and calling its ejbActivate( )
methods, loading each entity bean’s state by calling
its ejbLoad( )
method, calling the
ejbRemove( )
on all of the entity beans in the
cascade-delete relationship, and then deleting their data. This
process can continue in a chain until all the cascade-delete
operations of all the relationships have completed.
A bean is in the Ready state when it is associated with an EJB object
and is ready to service requests from the client. When the client
invokes a business method, like Ship.getName( )
,
on the bean’s remote or local reference (EJB
object), the method invocation is received by the EJB server and
delegated to the bean instance. The instance performs the method and
returns the results. As long as the bean instance is in the Ready
state, it can service all the business methods invoked by the client.
Business methods can be called zero or more times, in any order.
In addition to servicing business methods, an entity bean in the Ready state can execute select methods, which are called by the bean instance while servicing a business method.
The
ejbLoad( )
and
ejbStore( )
methods, which synchronize the bean
instance’s state with the database, can be called
only when the bean is in the Ready state. These methods can be called
in any order, depending on the vendor’s
implementation. Some vendors call ejbLoad( )
before every method invocation and ejbStore( )
after every method invocation, depending on the transactional
context. Other vendors call these methods less frequently.
In bean-managed persistence, the ejbLoad( )
method
should always use the EntityContext.getPrimaryKey( )
method to obtain data from the database and should not
trust any primary key or other data that the bean has stored in its
fields. (This is how we implemented it in the bean-managed version of
the Ship bean in Chapter 9.) It should be
assumed, however, that the state of the bean is valid when calling
the ejbStore( )
method.
In container-managed persistence, the ejbLoad( )
method is always called immediately following the synchronization of
the bean’s container-managed fields with the
database—in other words, right after the container updates the
state of the bean instance with data from the database. This provides
an opportunity to perform any calculations or reformat data before
the instance can service business-method invocations from the client.
The ejbStore( )
method is called just before the
database is synchronized with the state of the bean
instance—just before the container writes the container-managed
fields to the database. This provides the CMP entity bean instance
with an opportunity to change the data in the container-managed
fields prior to their persistence to the database.
In bean-managed persistence, the ejbLoad( )
and
ejbStore( )
methods are called when the container
deems it appropriate to synchronize the bean’s state
with the database. These are the only callback methods that should be
used to synchronize the bean’s state with the
database. Do not use ejbActivate( )
,
ejbPassivate( )
, setEntityContext( )
, or unsetEntityContext( )
to access
the database for the purpose of synchronization. You should use the
ejbCreate( )
and ejbRemove( )
methods, however, to insert and delete (respectively) the
entity’s data into and from the database.
A bean instance’s life cycle ends when the container decides to remove it from the pool and allow it to be garbage collected. This happens under a few different circumstances. If the container decides to reduce the number of instances in the pool—usually to conserve resources—it releases one or more bean instances and allows them to be garbage collected. The ability to adjust the size of the instance pool allows the EJB server to manage its resources (the number of threads, available memory, etc.) so that it can achieve the highest possible performance.
When an EJB server is shut down, most containers release all the bean
instances so that they can be safely garbage collected. Some
containers may also decide to release any instances that are behaving
unfavorably or that have suffered from some kind of unrecoverable
error that makes them unstable. For example, any time an entity bean
instance throws a type of RuntimeException
from
any of its methods, the EJB container will evict that instance from
memory and replace it with a stable instance from the instance pool.
When an entity bean instance leaves the instance pool to be garbage
collected, the
unsetEntityContext( )
method is invoked by the container to alert the bean instance that it
is about be destroyed. This callback method lets the bean instance
release any resources it maintains before being garbage collected.
Once the bean’s unsetEntityContext( )
method has been called, it is garbage collected.
The bean instance’s finalize( )
method may or may not be invoked following the
unsetEntityContext( )
method. A bean should not
rely on its finalize( )
method, since each vendor handles
evicting instances differently.
[27] Constructors should never be defined in the bean class. The default no-argument constructor, which is implicit if no other constructors are declared, must be available to the container.
[28] This is only a conceptual model. In reality, an EJB container and the EJB object may be the same thing, or a single EJB object may provide a multiplexing service for all entities of the same type. The implementation details are not as important as understanding the life-cycle protocol.