In EJB 1.0, the impact of exceptions on
transactions largely depends on who initiates the transaction. A
transaction that is started automatically when a bean method is
invoked is a container-initiated transaction. Specifying the
TX_REQUIRES_NEW
transaction attribute, for
example, always results in a container-initiated transaction. A
TX_REQUIRED
method invoked by a non-transactional
client also results in a container-initiated transaction. A
transaction that is started explicitly using JTA (on the client or in
a TX_BEAN_MANAGED
bean) is not a
container-initiated transaction.
With container-initiated transactions, any exception thrown during a transaction can cause the transaction to roll back. The impact of an exception thrown during a transaction depends on the type of exception (checked or unchecked) and the transaction attribute of the bean method throwing the exception. This section examines the different combinations of exceptions (checked or unchecked) and transaction attributes and their combined affect on transactional outcomes.
Any exception (application
exception, unchecked exception, or
RemoteException
) not handled
within the scope of the container-initiated transaction causes the
container to roll back the entire transaction.
An exception that is not handled within the scope of container-initiated transaction is an exception that is propagated, through the call stack, beyond the bean method that started the container-initiated transaction.
As an example, take another look at the
bookPassage()
method from the TravelAgent bean:
public Ticket bookPassage(CreditCard card, double price) throws IncompleteConversationalState, RemoteException { 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); return ticket; } catch(Exception e) { throw new RemoteException("",e); } }
The beans (Reservation and ProcessPayment) accessed by the
bookPassage()
method have a transaction attribute
of TX_REQUIRED
. This means that the transaction
associated with the bookPassage()
method will be
propagated to the methods called on the ProcessPayment and
Reservation beans. If bookPassage()
has a
transaction attribute of TX_REQUIRES_NEW
, then we
can assume that it will always be called in the scope of a
container-initiated transaction; when
bookPassage()
is invoked, a new
container-initiated transaction is created.
In container-initiated transactions, any application exception thrown within the scope of the container-initiated transaction does not cause a rollback. This provides the bean with an opportunity to recover and retry an operation. However, an application exception that is not handled within the scope of the container-initiated transaction does cause the transaction to be rolled back.
This can be demonstrated by redefining the
bookPassage()
method:
public Ticket bookPassage(CreditCard card, double price) throws IncompleteConversationalState { // EJB 1.0: also throws RemoteException 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(); try { process.byCredit(customer, card, price); } catch(PaymentException pe) { // Attempt to recover. } Ticket ticket = new Ticket(customer,cruise,cabin,price); return ticket; } catch(Exception e){ // EJB 1.0: throw new RemoteException("",e); throw new EJBException(e); } }
Here, the byCredit()
method of the ProcessPayment
bean has been wrapped in its own exception-handling logic; we can
imagine some sophisticated code in the catch
block
that evaluates the problem and attempts to reinvoke the method. In
this case, the PaymentException
thrown by the
byCredit()
method does not cause the
bookPassage()
container-initiated transaction to
be rolled back because the application exception is thrown from
within the container-initiated scope. However, if an
IncompleteConversationalState
exception is thrown
by the bookPassage()
method itself, the
transaction will be rolled back. Once an exception is thrown by the
bookPassage()
method, and therefore
not handled within the scope of the
container-initiated transaction, the transaction will be rolled back.
These rules for application exceptions also apply to
RemoteException
types. If a
RemoteException
is thrown by a bean or by some
other resource (JNDI, for example), that exception does not
automatically cause a rollback. Rollback only occurs if the remote
exception is not handled within of the scope of
the container-initiated
transaction.
When a transaction is propagated from a
client to a bean, the client defines the scope of the transaction.
This means that application exceptions and
RemoteExceptions
thrown by the beans do not
automatically cause the transaction to be rolled back. Again,
consider the possibility that the byCredit()
method throws an application exception. If the client initiated the
transaction, it’s the client’s responsibility to
determine whether the operation can be retried and to roll back if
appropriate. Similarly, if the bookPassage()
method had a transaction attribute of TX_REQUIRED
or TX_SUPPORTS
and was invoked from a
client-initiated transaction (perhaps the client uses JTA), then any
application exception or RemoteException
thrown
from within the bookPassage()
method will not
automatically cause a rollback. The client must handle the exception
and determine for itself if a rollback is appropriate.
The effect of exceptions is
different on bean-managed transactions
(TX_BEAN_MANAGED
). With
TX_BEAN_MANAGED
entity beans and stateless session
beans, any exception thrown by the bean method causes the transaction
to be rolled back by the container. Remember that with
TX_BEAN_MANAGED
, transactions must begin and be
completed within the same method. If an exception unexpectedly ends
the method, the container rolls back the transaction.
With TX_BEAN_MANAGED
stateful session beans, only
unchecked exceptions thrown by bean methods cause the container to
roll back the transaction. Any other exception thrown by the bean
method, whether it be an application exception or
RemoteException
, does not affect the transaction.
This can cause major headaches if the exception is thrown before an
intended commit was reached. For this reason, beans that manage their
own transactions must be extremely careful about exception handling.
The EJBContext
rollback methods can be used in
these situations.
Regardless of the method’s transaction attribute (unless
it’s TX_NOT_SUPPORTED
), an
unchecked exception causes the
transaction to be rolled back, regardless of whether the transaction
is container-initiated, client-initiated, or bean-managed.
Unchecked exceptions thrown by a bean in the scope of a transaction
always cause a rollback. In addition, the container intercepts the
unchecked exception and rethrows it to the bean’s client
as a
javax.transaction.TransactionRolledbackException
.[42]
[42] Bean methods with the TX_SUPPORTS
attribute
that are invoked by non-transactional clients are not included in
this policy. They simply throw the unchecked exception as a
RemoteException
.