One of the primary advantages of Enterprise JavaBeans is that it allows for declarative transaction management. Without this feature, transactions must be controlled using explicit transaction demarcation. This involves the use of fairly complex APIs like the OMG’s Object Transaction Service (OTS) or its Java implementation, the Java Transaction Service ( JTS). Explicit demarcation is difficult for developers to use at best, particularly if you are new to transactional systems. In addition, explicit transaction demarcation requires that the transactional code be written within the business logic, which reduces the clarity of the code and, more importantly, creates inflexible distributed objects. Once transaction demarcation is hardcoded into the business object, changes in transaction behavior require changes to the business logic itself. We talk more about explicit transaction management and EJB later in this chapter.
With declarative transaction management, the transactional behavior of EJBs can be controlled using the deployment descriptor, which sets transaction attributes for individual enterprise bean methods. This means that the transactional behavior of an EJB can be changed without changing the EJB’s business logic. In addition, an EJB deployed in one application can be defined with different transactional behavior than the same EJB deployed in a different application. Declarative transaction management reduces the complexity of transactions for EJB developers and application developers and makes it easier to create robust transactional applications.
Transaction scope is a crucial concept for understanding transactions. In this context, transaction scope means those EJBs—both session and entity—that are participating in a particular transaction.
In the bookPassage()
method of the TravelAgent
EJB, all the EJBs involved are part of the same transaction scope.
The scope of the transaction starts when a client invokes the
TravelAgent EJB’s bookPassage()
method. Once
the transaction scope has started, it is
propagated to
both the newly created Reservation EJB and the ProcessPayment EJB.
As you know, a transaction is a unit-of-work made up of one or more tasks. In a transaction, all the tasks that make up the unit-of-work must succeed for the entire transaction to succeed; the transaction must be atomic. If any task fails, the updates made by all the other tasks in the transaction will be rolled back or undone. In EJB, tasks are expressed as enterprise bean methods, and a unit-of-work consists of every enterprise bean method invoked in a transaction. The scope of a transaction includes every EJB that participates in the unit-of-work.
It is easy to trace the scope of a transaction by following the
thread of execution. If the invocation of the
bookPassage()
method begins a transaction, then
logically, the transaction ends when the method completes. The scope
of the bookPassage()
transaction would include the
TravelAgent, Reservation, and ProcessPayment EJBs—every EJB
touched by the bookPassage()
method. A transaction
is propagated to an EJB when that EJB’s method is invoked and
included in the scope of that transaction.
A transaction can end if an exception is thrown while the
bookPassage()
method is executing. The exception
can be thrown from one of the other EJBs or from the
bookPassage()
method itself. An exception may or
may not cause a rollback, depending on its type. We’ll discuss
exceptions and transactions in more detail later.
The thread of execution is not the only factor that determines whether an EJB is included in the scope of a transaction; the EJB’s transaction attributes also play a role. Determining whether an EJB participates in the transaction scope of any unit-of-work is accomplished either implicitly using the EJB’s transaction attributes or explicitly using the Java Transaction API ( JTA).
As an application developer, you do not normally need to control transactions explicitly when using an EJB server. EJB servers can manage transactions implicitly, based on the transaction attributes established for EJBs at deployment time. The ability to specify how business objects participate in transactions through attribute-based programming is a common characteristic of CTMs, and one of the most important features of the EJB component model.
When an EJB is deployed, you can set its runtime transaction attribute in the deployment descriptor to one of several values. The following list shows the XML attribute values used to specify these transaction attributes:
NotSupported
Supports
Required
RequiresNew
Mandatory
Never
Using transaction attributes simplifies building transactional applications by reducing the risks associated with improper use of transactional protocols such as JTA (discussed later in this chapter). It’s more efficient and easier to use transaction attributes than to control transactions explicitly.
You can set a transaction attribute for the entire EJB (in which case it applies to all methods) or to set different transaction attributes for individual methods. The former method is much simpler and less error prone, but setting attributes at the method level offers more flexibility. The code fragments in the following sections show how to set the default transaction attribute of an EJB in the EJB’s deployment descriptor.
In the XML deployment descriptor, a
<container-transaction>
element specifies the transaction
attributes for the EJBs described in the deployment descriptor:
<ejb-jar> ... <assembly-descriptor> ... <container-transaction> <method> <ejb-name>TravelAgentEJB</ejb-name> <method-name> * </method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>TravelAgentEJB</ejb-name> <method-name>listAvailableCabins</method-name> </method> <trans-attribute>Supports</trans-attribute> </container-transaction> ... </assembly-descriptor> ... </ejb-jar>
This deployment descriptor specifies the transaction attributes for
the TravelAgent EJB. Each
<container-transaction>
element specifies a
method and the transaction attribute that should be applied to that
method. The first <container-transaction>
element specifies that all methods by default have a transaction
attribute of Required
; the
*
is a wildcard that
indicates all the methods of the TravelAgent EJB. The second
<container-transaction>
element overrides
the default setting to specify that the
listAvailableCabins()
method will have a
Supports
transaction attribute. Note that we have
to specify which EJB we are referring to with the
<ejb-name>
element; an XML deployment descriptor can cover many EJBs.
Here are the definitions of the transaction
attributes listed earlier. In a few of the definitions, we say that
the client transaction is
suspended
.
This means that the transaction is not propagated to the enterprise
bean method being invoked; propagation of the transaction is
temporarily halted until the enterprise bean method returns. To make
things easier, we will talk about attribute types as if they were
bean types: for example, we’ll say a
"Required
EJB” as shorthand for
“an enterprise bean with the Required
transaction attribute.” The attributes are:
NotSupported
Invoking a method on an EJB with this transaction attribute suspends
the transaction until the method is completed. This means that the
transaction scope is not propagated to the
NotSupported
EJB or
any of the EJBs it calls. Once the method on the
NotSupported
EJB is done, the original transaction
resumes its execution.
Figure 14-1 shows that a
NotSupported
EJB does not propagate the client
transaction when one of its methods is invoked.
Supports
This attribute means that the enterprise bean method will be included
in the transaction scope if it is invoked within a transaction. In
other words, if the EJB or client that invokes the
Supports
EJB is
part of a transaction scope, the Supports
EJB and
all EJBs accessed by it become part of the original transaction.
However, the Supports
EJB doesn’t have to be
part of a transaction and can interact with clients and other EJBs
that are not included in a transaction scope.
Figure 14-2(a) shows the Supports
EJB being invoked by a transactional client and propagating the
transaction. Figure 14-2(b) shows the
Supports
EJB being invoked by a nontransactional
client.
Required
This attribute means that the enterprise bean method must be invoked
within the scope of a transaction. If the calling client or EJB is
part of a transaction, the
Required
EJB is
automatically included in its transaction scope. If, however, the
calling client or EJB is not involved in a transaction, the
Required
EJB starts its own new transaction. The
new transaction’s scope covers only the
Required
EJB and all other EJBs accessed by it.
Once the method invoked on the Required
EJB is
done, the new transaction’s scope ends.
Figure 14-3(a) shows the Required
EJB being invoked by a transactional client and propagating the
transaction.
Figure 14-3(b) shows the Required
EJB being invoked by a nontransactional client, which causes it to
start its own transaction.
RequiresNew
This attribute means that a new transaction is always started.
Regardless of whether the calling client or EJB is part of a
transaction, a method with the
RequiresNew
attribute begins a new transaction when invoked. If the calling
client is already involved in a transaction, that transaction is
suspended until the RequiresNew
EJB’s method
call returns. The new transaction’s scope covers only the
RequiresNew
EJB and all the EJBs accessed by it.
Once the method invoked on the
RequiresNew
EJB is done, the
new transaction’s scope ends and the original transaction
resumes.
Figure 14-4(a) shows the
RequiresNew
EJB being invoked by a transactional
client. The client’s transaction is suspended while the EJB
executes under its own transaction. Figure 14-4(b)
shows the RequiresNew
EJB being invoked by a
nontransactional client; the RequiresNew
EJB
executes under its own transaction.
Mandatory
This attribute means that the enterprise
bean method must always be made part of the transaction scope of the
calling client. If the calling client or EJB is not part of a
transaction, the invocation will fail, throwing a
javax.transaction.TransactionRequiredException
to
remote clients or a
javax.ejb.TransactionRequiredLocalException
to
local EJB 2.0 clients.
Figure 14-5(a) shows the
Mandatory
EJB being invoked by a transactional
client and propagating the transaction. Figure 14-5(b) shows the Mandatory
EJB
being invoked by a nontransactional client; the method throws a
TransactionRequiredException
to remote clients or
a TransactionRequredLocalException
to local EJB
2.0 clients, because there is no transaction scope.
Never
This attribute means that the enterprise
bean method must not be invoked within the scope of a transaction. If
the calling client or EJB is part of a transaction, the
Never
EJB will throw a
RemoteException
to remote clients or an
EJBException
to local EJB 2.0 clients. However, if
the calling client or EJB is not involved in a transaction the
Never
EJB will execute normally without a
transaction.
Figure 14-6(a) shows the Never
EJB being invoked by a nontransactional client. Figure 14-6(b) shows the Never
EJB
being invoked by transactional client; the method throws a
RemoteException
to remote clients or an
EJBException
to local EJB 2.0 clients, because the
method can never be invoked by a client or EJB that is included in a
transaction.
The EJB 2.0 specification strongly advises that
CMP 2.0 entity beans use only the
Required
, RequiresNew
, and
Mandatory
transaction attributes. This restriction
ensures that all database access occurs in the context of a
transaction, which is important when the container is automatically
managing persistence. While the specification requires that these
three transaction attributes be supported for CMP 2.0, support for
the Never
, Supports
, and
NotSupported
transaction attributes is optional.
If a vendor wishes to support these attributes (which allow the bean
to execute without a transaction) they may do so, but it’s not
recommended. Consult your vendor’s documentation to determine
if they support the optional transaction attributes. This book
recommends that you use only Required
,
RequiresNew
, or Mandatory
with
EJB 2.0 container-managed persistence entity beans.
Message-driven beans may declare only the
NotSupported
or Required
transaction attributes. The other transaction attributes don’t
make sense in message-driven beans because they apply to
client-initiated transactions. The Supports
,
RequiresNew
, Mandatory
, and
Never
attributes are all relative to the
transaction context of the client. For example, the
Mandatory
attribute requires the client to have a
transaction in progress before calling the enterprise bean. This is
meaningless for a message-driven bean, which is decoupled from the
client.
The NotSupported
transaction attribute indicates
that the message will be processed without a transaction. The
Required
transaction attribute indicates that the
message will be processed with a container-initiated
transaction.
To
illustrate the impact of transaction attributes on enterprise bean
methods, we’ll look once again at the
bookPassage()
method of the TravelAgent EJB
created in Chapter 12 (see the listings earlier in
this chapter).
In order for bookPassage()
to execute as a
successful transaction, both the creation of the Reservation EJB and
the charge to the customer must be successful. This means that both
operations must be included in the same transaction. If either
operation fails, the entire transaction fails. We could have
specified the Required
transaction attribute as
the default for all the EJBs involved, because that attribute
enforces our desired policy that all EJBs must execute within a
transaction and thus ensures data consistency.
As a transaction monitor, an EJB server watches each method call in
the transaction. If any of the updates fail, all the updates to all
the EJBs will be reversed or rolled back
. A
rollback is like an undo
command. If you have
worked with relational databases, the concept of a rollback should be
familiar to you. Once an update is executed, you can either commit
the update or roll it back. A commit makes the changes requested by
the update permanent; a rollback aborts the update and leaves the
database in its original state. Making EJBs transactional provides
the same kind of rollback/commit control. For example, if the
Reservation EJB cannot be created, the charge made by the
ProcessPayment EJB is rolled back. Transactions make updates an
all-or-nothing proposition. This ensures that the unit-of-work, like
the bookPassage()
method, executes as intended,
and it prevents inconsistent data from being written to databases.
In cases in which the container implicitly manages the transaction, the commit and rollback decisions are handled automatically. When transactions are managed explicitly within an enterprise bean or by the client, the responsibility falls on the enterprise bean or application developer to commit or roll back a transaction. Explicit demarcation of transactions is covered in detail later in this chapter.
Let’s assume that the TravelAgent EJB is created and used on a client as follows:
TravelAgent agent = agentHome.create(customer); agent.setCabinID(cabin_id); agent.setCruiseID(cruise_id); try { agent.bookPassage(card,price); } catch(Exception e) { System.out.println("Transaction failed!"); }
Furthermore, let’s assume that the
bookPassage()
method has been given the
transaction attribute
RequiresNew
. In this case,
the client that invokes the bookPassage()
method
is not itself part of a transaction. When
bookPassage()
is invoked on the TravelAgent EJB, a
new transaction is created, as dictated by the
RequiresNew
attribute. This means that the
TravelAgent EJB registers itself with the EJB server’s
transaction manager, which will manage the transaction automatically.
The transaction manager coordinates transactions, propagating the
transaction scope from one EJB to the next to ensure that all EJBs
touched by a transaction are included in the transaction’s
unit-of-work. That way, the transaction manager can monitor the
updates made by each enterprise bean and decide, based on the success
of those updates, whether to commit all changes made by all
enterprise beans to the database or roll them all back. If a
system exception is thrown by the
bookPassage()
method, the transaction is
automatically rolled back. We will talk more about exceptions later
in this chapter.
When the byCredit()
method is invoked within the
bookPassage()
method, the ProcessPayment EJB
registers with the transaction manager under the transactional
context that was created for the TravelAgent EJB; the transactional
context is propagated to the ProcessPayment EJB. When the new
Reservation EJB is created, it is also registered with the
transaction manager under the same transaction. When all the EJBs are
registered and their updates are made, the transaction manager checks
to ensure that their updates will work. If all the updates will work,
the transaction manager allows the changes to become permanent. If
one of the EJBs reports an error or fails, any changes made by either
the ProcessPayment or Reservation EJB are rolled back by the
transaction manager. Figure 14-7 illustrates the
propagation and management of the TravelAgent EJB’s
transactional context.
In addition to managing transactions in its own environment, an EJB server can coordinate with other transactional systems. If, for example, the ProcessPayment EJB actually came from a different EJB server than the TravelAgent EJB, the two EJB servers would cooperate to manage the transaction as one unit-of-work. This is called a distributed transaction .[55]
A distributed transaction is a great deal more complicated, requiring what is called a two-phase commit (2-PC or TPC). 2-PC is a mechanism that allows transactions to be managed across different servers and resources (e.g., databases and JMS providers). The details of a 2-PC are beyond the scope of this book, but a system that supports it will not require any extra operations by an EJB or application developer. If distributed transactions are supported, the protocol for propagating transactions, as discussed earlier, will be supported. In other words, as an application or EJB developer, you should not notice a difference between local and distributed transactions.
In EJB 2.0 container-managed persistence,
collection-based relationships may only be
accessed within a single transaction. In other words, it’s
illegal to obtain a Collection
object from a
collection-based relationship field in one transaction and then use
it in another.
For example, if an enterprise bean accesses another’s
collection-based relationship field through its local interface, the
Collection
returned from the accessor method can
be used only within the same transaction:
public class HypotheticalBean implements javax.ejb.EntityBean { public void methodX(CustomerLocal customer) { Collection reservations = customer.getReservations(); Iterator iterator = reservations.iterator; while(iterator.hasNext()){ ... ... } ... }
If the Customer EJB’s getReservations()
method was declared with a transaction attribute of
RequiresNew
, attempting to invoke any methods on
the Collection
, including the
iterator()
method, will result in a
java.lang.IllegalStateException
. This exception is
thrown because the Collection
object was created
within the scope of the getReservations()
transaction, not in the scope of methodX()
’s
transaction. The transaction context of methodX()
is different from the transaction context of the
getReservations()
method.
The Collection
from an entity bean can be used by
another co-located bean only if it is obtained and accessed in the
same transaction context. As long as the Customer EJB’s
getReservations()
method propagates the
transaction context of methodX()
, the
Collection
can be used without any problems. This
can be accomplished by changing the
getReservations()
method so that it declares its
transaction attribute as Required
or
Mandatory
.