Although this section covers JTA, it is strongly recommended that you do not attempt to manage transactions explicitly. Through transaction attributes, EJB provides a comprehensive and simple mechanism for delimiting transactions at the method level and propagating transactions automatically. Only developers with a thorough understanding of transactional systems should attempt to use JTA with EJB.
In EJB, implicit transaction management is provided on the bean method level so that we can define transactions that are delimited by the scope of the method being executed. This is one of the primary advantages of EJB over cruder distributed object implementations: it reduces complexity and therefore programmer error. In addition, declarative transaction demarcation, as used in EJB, separates the transactional behavior from the business logic; a change to transactional behavior does not require changes to the business logic. In rare situations, however, it may be necessary to take control of transactions explicitly. To do this, it is necessary to have a much more complete understanding of transactions.
Explicit management of transactions is complex and is normally accomplished using the OMG’s OTS (Object Transaction Service) or the Java implementation of OTS, JTS ( Java Transaction Service). OTS and JTS provide APIs that allow developers to work with transaction managers and resources (databases) directly. While the JTS implementation of OTS is robust and complete, it is not the easiest API to work with; it requires clean and intentional control over the bounds of enrollment in transactions.
Enterprise JavaBeans supports a much
simpler API, the Java Transaction API (JTA), for working with
transactions.[39] This API is implemented by the
javax.transaction
package. JTA actually consists
of two components: a high-level transactional client interface and a
low-level X/Open XA interface. We are concerned with the high-level
client interface since that is the one accessible to the beans and is
the recommended transactional interface for client applications. The
low-level XA interface is used by the EJB server and container to
automatically coordinate transactions with resources like databases.
As an application and bean developer, you will not work with the XA
interface in JTA. Instead, your use of explicit transaction
management will focus on one very simple interface:
javax.transaction.UserTransaction
. UserTransaction
provides an interface to the transaction manager that allows the
application developer to manage the scope of a transaction
explicitly. Here is an example of how explicit demarcation might be
used in a bean or client application:
// EJB 1.0: Use native casting instead of narrow() Object ref = getInitialContext().lookup("travelagent.Home"); TravelAgentHome home = (TravelAgentHome) PortableRemoteObject.narrow(ref,TravelAgentHome.class); TravelAgent tr1 = home.create(customer); tr1.setCruiseID(cruiseID); tr1.setCabinID(cabin_1); TravelAgent tr2 = home.create(customer); tr2.setCruiseID(cruiseID); tr2.setCabinID(cabin_2); javax.transaction.UserTransaction tran = ...; // Get the UserTransaction. tran.begin(); tr1.bookPassage(visaCard,price); tr2.bookPassage(visaCard,price); tran.commit();
The client application needs to book two cabins for the same
customer—in this case, the customer is purchasing a cabin for
himself and his children. The customer doesn’t want to book
either cabin unless he can get both, so the client application is
designed to include both bookings in the same transaction. Explicitly
marking the transaction’s boundaries through the use of the
javax.transaction.UserTransaction
object does
this. Each bean method invoked by the current thread between the
UserTransaction.begin()
and
UserTransaction.commit()
method is included in the
same transaction scope, according to transaction attribute of the
bean methods invoked.
Obviously this example is contrived, but the point it makes is clear.
Transactions can be controlled directly, instead of depending on
method scope to delimit them. The advantage of using explicit
transaction demarcation is that it gives the client control over the
bounds of a transaction. The client, in this case, may be a client
application or another bean.[40] In either case, the same
javax.transaction.UserTransaction
is used, but it
is obtained from different sources depending on whether it is needed
on the client or in a bean class.
Java 2 Enterprise Edition (J2EE) specifies how a client application
can obtain a UserTransaction
object using JNDI.
Here’s how a client obtains a
UserTransaction
object if the EJB 1.1 container is
part of a J2EE system:
... Context jndiCntx = new InitialContext(); UserTransaction tran = (UserTransaction)jndiCntx.lookup("java:comp/UserTransaction"); utx.begin(); ... utx.commit(); ...
J2EE and its relationship with EJB 1.1 is covered in more detail in Chapter 11.
EJB 1.0 doesn’t specify how client applications should obtain a
reference to a UserTransaction
. Many vendors make
the UserTransaction
available to the client
application through JNDI. Here’s how a client application
obtains a UserTransaction
using JNDI:
UserTransaction ut = (UserTransaction) jndiContext.lookup("javax.transaction.UserTransaction");
EJB servers may use other mechanisms, such as a proprietary API or
casting the home interface into a UserTransaction
.
Beans can also manage transactions
explicitly. In EJB 1.1, only
session beans with the
<transaction-type>
value of “Bean” can be
bean-managed transaction beans. Entity beans can never be
bean-managed transaction beans. Beans that manage their own
transactions do not declare transaction attributes for their methods.
Here’s how an EJB 1.1 session bean declares that it will manage
transactions explicitly:
<ejb-jar> <enterprise-beans> ... <session> ... <transaction-type>Bean</transaction-type> ...
In EJB 1.0, only beans with the
transaction attribute TX_BEAN_MANAGED
for its
methods are considered bean-managed transaction beans. Entity beans
as well as session beans can manage their own transactions.
To manage its own transaction, a bean needs to obtain a
UserTransaction
object. A bean obtains a reference
to the UserTransaction
from the
EJBContext
, as shown below:
public class HypotheticalBean extends SessionBean { SessionContext ejbContext; public void someMethod() { try { UserTransaction ut = ejbContext.getUserTransaction(); ut.begin(); // Do some work. ut.commit(); } catch(IllegalStateException ise) {...} catch(SystemException se) {...} catch(TransactionRolledbackException tre) {...} catch(HeuristicRollbackException hre) {...} catch(HeuristicMixedException hme) {...}
An EJB 1.1 bean can access the UserTransaction
from the EJBContext
as shown in the previous
example or from the JNDI ENC as shown in the following example. Both
methods are legal and proper in EJB 1.1. The bean performs the lookup
using the "java:comp/env/UserTransaction
" context:
InitialContext jndiCntx = new InitialContext(); UserTransaction tran = (UserTransaction) jndiCntx.lookup("java:comp/env/UserTransaction");
With
stateless session beans (and
entity beans in EJB
1.0), transactions that are managed using the
UserTransaction
must be started and completed
within the same method, as shown previously. In other words,
UserTransaction
transactions cannot be started in
one method and ended in another. This makes sense because both entity
and stateless session bean instances are shared across many clients.
With stateful session beans, however, a
transaction can begin in one method and be committed in another
because a stateful session bean is only used by one client. This
allows a stateful session bean to associate itself with a transaction
across several different client-invoked methods. As an example,
imagine the TravelAgent bean as a bean-managed transaction bean. In
the following code, the transaction is started in the
setCruiseID()
method and completed in the
bookPassage()
method. This allows the TravelAgent
bean’s methods to be associated with the same transaction.
public class TravelAgentBean implements javax.ejb.SessionBean { public Customer customer; public Cruise cruise; public Cabin cabin; public javax.ejb.SessionContext ejbContext; ... public void setCruiseID(int cruiseID) throws javax.ejb.FinderException{ // EJB 1.0: also throws RemoteException try { ejbContext.getUserTransaction().begin(); CruiseHome home = (CruiseHome)getHome("CruiseHome", CruiseHome.class); cruise = home.findByPrimaryKey(new CruisePK(cruiseID)); } catch(Exception re) { // EJB 1.0: throw new RemoteException("",re); throw new EJBException(re); } } public Ticket bookPassage(CreditCard card, double price) throws IncompleteConversationalState { // EJB 1.0: also throws RemoteException try { if (ejbContext.getUserTransaction().getStatus() != javax.transaction.Status.STATUS_ACTIVE) { // EJB 1.0: throw new RemoteException("Transaction is not active"); throw new EJBException("Transaction is not active"); } } catch(javax.transaction.SystemException se) { // EJB 1.0: throw new RemoteException("",se); throw new EJBException(se); } if (customer == null || cruise == null || cabin == null) { throw new IncompleteConversationalState(); } try { ReservationHome resHome = (ReservationHome) getHome("ReservationHome",ReservationHome.class); Reservation reservation = resHome.create(customer, cruise, cabin, price); ProcessPaymentHome ppHome = (ProcessPaymentHome) getHome("ProcessPaymentHome",ProcessPaymentHome.class); ProcessPayment process = ppHome.create(); process.byCredit(customer, card, price); Ticket ticket = new Ticket(customer,cruise,cabin,price); ejbContext.getUserTransaction().commit(); return ticket; } catch(Exception e) { // EJB 1.0: throw new RemoteException("",e); throw new EJBException(e); } } ... }
Repeated calls to the
EJBContext.getUserTransaction()
method return a reference to the
same UserTransaction
object. The container is
required to retain the association between the transaction and the
stateful bean instance across multiple client calls until the
transaction terminates.
In the bookPassage()
method, we can check the
status of the transaction to ensure that it’s still active. If
the transaction is no longer active, we throw an exception. The use
of the getStatus()
method is covered in more
detail later in this chapter.
When a bean-managed transaction method is invoked by a client that is already involved in a transaction, the client’s transaction is suspended until the bean method returns. This suspension occurs whether the bean-managed transaction bean explicitly starts its own transaction within the method or, in the case of stateful beans, the transaction was started in a previous method invocation. The client transaction is always suspended until the bean-managed transaction method returns.
Transactions are normally controlled by a transaction manager (often the EJB server) that manages the ACID characteristics across several beans, databases, and servers. This transaction manager uses a two-phase commit (2-PC) to manage transactions. 2-PC is a protocol for managing transactions that commits updates in two stages. 2-PC is complex and outside the scope of this book, but basically it requires that servers and databases cooperate to ensure that all the data is made durable together. Some EJB servers support 2-PC while others don’t, and the value of this transaction mechanism is a source of some debate. The important point to remember is that a transaction manager controls the transaction; based on the results of a poll against the resources (databases and other servers), it decides whether all the updates should be committed or rolled back. A heuristic decisionis when one of the resources makes a unilateral decision to commit or roll back without permission from the transaction manager. Once a heuristic decision has been made, the atomicity of the transaction is lost and possible data integrity errors can occur.
UserTransaction
, discussed in the next section,
throws a couple of different exceptions related to heuristic
decisions; these are included in the following discussion.
UserTransaction
is
a Java interface that is defined in the following code. In EJB 1.0,
the EJB server is only required to support the functionality of this
interface and the Status
interface discussed here.
EJB servers are not required to support the rest of JTA, nor are they
required to use JTS for their transaction service. The
UserTransaction
is
defined as follows:
public interface javax.transaction.UserTransaction { public abstract voidbegin
() throws IllegalStateException, SystemException; public abstract voidcommit
() throws IllegalStateException, SystemException, TransactionRolledbackException, HeuristicRollbackException, HeuristicMixedException; public abstract intgetStatus
(); public abstract voidrollback
() throws IllegalStateException, SecurityException, SystemException; public abstract voidsetRollbackOnly
() throws IllegalStateException, SystemException; public abstract voidsetTransactionTimeout
(int seconds) throws SystemException; }
Here’s what the methods defined in this interface do:
begin()
Invoking the begin()
method creates a new
transaction. The thread that executes the begin()
method is immediately associated with the new transaction. The
transaction is propagated to any bean that supports existing
transactions. The begin()
method can throw one of
two checked exceptions. IllegalStateException
is
thrown when begin()
is called by a thread that is
already associated with a transaction. You must complete any
transactions associated with that thread before beginning a new
transaction. SystemException
is thrown if the
transaction manager (the EJB server) encounters an unexpected error
condition.
commit()
The commit()
method completes the transaction that
is associated with the current thread. When
commit()
is executed, the current thread is no
longer associated with a transaction. This method can throw several
checked exceptions. IllegalStateException
is
thrown if the current thread is not associated with a transaction.
SystemException
is thrown if the transaction
manager (the EJB server) encounters an unexpected error condition.
TransactionRolledbackException
is thrown when the
entire transaction is rolled back instead of committed; this can
happen if one of the resources was unable to perform an update or if
the UserTransaction.rollBackOnly()
method was
called. HeuristicRollbackException
indicates that
heuristic decisions were made by one or more resources to roll back
the transaction. HeuristicMixedException
indicates
that heuristic decisions were made by resources to both roll back and
commit the transaction; some resources decided to roll back while
others decided to commit.
rollback()
The rollback()
method is invoked to roll back the
transaction and undo updates. The rollback()
method can throw one of three different checked exceptions.
SecurityException
is thrown if the thread using
the UserTransaction
object is not allowed to roll
back the transaction. IllegalStateException
is
thrown if the current thread is not associated with a transaction.
SystemException
is thrown if the transaction
manager (the EJB server) encounters an unexpected error condition.
setRollBackOnly()
This method is invoked to mark the transaction for rollback. This
means that, whether or not the updates executed within the
transaction succeed, the transaction must be rolled back when
completed. This method can be invoked by any
TX_BEAN_MANAGED
bean participating in the
transaction or by the client application. The
setRollBackOnly()
method can throw one of two
different checked exceptions.
IllegalStateException
is thrown if the current
thread is not associated with a transaction.
SystemException
is thrown if the transaction
manager (the EJB server) encounters an unexpected error condition.
setTransactionTimeout(int seconds)
This method sets the life span of a transaction: how long it will
live before timing out. The transaction must complete before the
transaction timeout is reached. If this method is not called, the
transaction manager (EJB server) automatically sets the timeout. If
this method is invoked with a value of
seconds, the default timeout of the transaction manager will be used.
This method must be invoked after the begin()
method. SystemException
is thrown if the
transaction manager (EJB server) encounters an unexpected error
condition.
getStatus()
The getStatus()
method returns an integer
that can be compared to constants defined in the
javax.transaction.Status
interface. This method
can be used by a sophisticated programmer to determine the status of
a transaction associated with a UserTransaction
object. SystemException
is thrown if the
transaction manager (EJB server) encounters an unexpected error
condition.
Status
is a
simple interface that contains no methods, only constants. Its sole
purpose is to provide a set of constants that describe the current
status of a transactional object—in this case, the
UserTransaction
:
interface javax.transaction.Status { public final static int STATUS_ACTIVE; public final static int STATUS_COMMITTED; public final static int STATUS_COMMITTING; public final static int STATUS_MARKED_ROLLBACK; public final static int STATUS_NO_TRANSACTION; public final static int STATUS_PREPARED; public final static int STATUS_PREPARING; public final static int STATUS_ROLLEDBACK; public final static int STATUS_ROLLING_BACK; public final static int STATUS_UNKNOWN; }
The value returned by
getStatus()
tells the client using the
UserTransaction
the status of a transaction.
Here’s what the constants mean:
STATUS_ACTIVE
An active transaction is associated with the
UserTransaction
object. This status is returned
after a transaction has been started and prior to a transaction
manager beginning a 2-PC commit. (Transactions that have been
suspended are still considered active.)
STATUS_COMMITTED
A transaction is associated with the
UserTransaction
object; the transaction has been
committed. It is likely that heuristic decisions have been made;
otherwise, the transaction would have been destroyed and the
STATUS_NO_TRANSACTION
constant would have been
returned instead.
STATUS_COMMITTING
A transaction is associated with the
UserTransaction
object; the transaction is in the
process of committing. The UserTransaction
object
returns this status if the transaction manager has decided to commit
but has not yet completed the process.
STATUS_MARKED_ROLLBACK
A transaction is associated with the
UserTransaction
object; the transaction has been
marked for rollback, perhaps as a result of a
UserTransaction.setRollbackOnly()
operation
invoked somewhere else in the application.
STATUS_NO_TRANSACTION
No transaction is currently associated with the
UserTransaction
object. This occurs after a
transaction has completed or if no transaction has been created. This
value is returned rather than throwing an
IllegalStateException
.
STATUS_PREPARED
A transaction is associated with the
UserTransaction
object. The transaction has been
prepared, which means that the first phase of the two-phase commit
process has completed.
STATUS_PREPARING
A transaction is associated with the
UserTransaction
object; the transaction is in the
process of preparing, which means that the transaction manager is in
the middle of executing the first phase of the two-phase commit.
STATUS_ROLLEDBACK
A transaction is associated with the
UserTransaction
object; the outcome of the
transaction has been identified as a rollback. It is likely that
heuristic decisions have been made; otherwise, the transaction would
have been destroyed and the STATUS_NO_TRANSACTION
constant would have been returned.
STATUS_ROLLING_BACK
A transaction is associated with the
UserTransaction
object; the transaction is in the
process of rolling back.
STATUS_UNKNOWN
A transaction is associated with the
UserTransaction
object; its current status cannot
be determined. This is a transient condition and subsequent
invocations will ultimately return a different
status.
Only beans that manage their own
transactions have access to the User-Transaction
from the EJBContext
. To provide other types of
beans with an interface to the transaction, the
EJBContext
interface provides the methods
setRollbackOnly()
and
getRollbackOnly()
.
The
setRollbackOnly()
method gives a bean the power to
veto a transaction. This power can be used if the bean detects a
condition that would cause inconsistent data to be committed when the
transaction completes. Once a bean invokes the
setRollbackOnly()
method, the current transaction
is marked for rollback and cannot be committed by any other
participant in the transaction—including the container.
The
getRollbackOnly()
method returns
true
if the current transaction has been marked
for rollback. This can be used to avoid executing work that
wouldn’t be committed anyway. If, for example, an exception is
thrown and captured within a bean method, this method can be used to
determine whether the exception caused the current transaction to be
rolled back. If it did, there is no sense in continuing the
processing. If it didn’t, the bean has an opportunity to
correct the problem and retry the task that failed. Only expert bean
developers should attempt to retry tasks within a transaction.
Alternatively, if the exception didn’t cause a rollback
(getRollbackOnly()
returns
false
), a rollback can be forced using the
setRollbackOnly()
method
.
Beans that manage their own transaction must not
use the setRollbackOnly()
and
getRollbackOnly()
methods of the
EJBContext
. These beans should use the
getStatus()
and rollback()
methods on the UserTransaction
object to check for
rollback and force a rollback
respectively.
[39] Enterprise JavaBeans 1.0 originally
specified JTS as the transitional API for explicit demarcation. JTA,
which was released after EJB, is the preferred API in both EJB 1.0
and EJB 1.1. Both JTS and JTA, however, use the
UserTransaction
interface, and so the information
here is applicable to servers that support either API.
[40] Only beans declared as
managing their own transactions (bean-managed transaction beans) can
use the UserTransaction
interface.