NOTE
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.[4] 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.
[4] 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.
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.[5] 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.
[5] Only beans declared as managing their own transactions (bean-managed transaction beans) can use the UserTransaction interface.
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, "Java 2, Enterprise Edition".
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.
NOTE
Transaction control across methods is strongly discouraged because it can result in improperly managed transactions and long-lived transactions that lock up resources.
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 void begin() throws IllegalStateException, SystemException; public abstract void commit() throws IllegalStateException, SystemException, TransactionRolledbackException, HeuristicRollbackException, HeuristicMixedException; public abstract int getStatus(); public abstract void rollback() throws IllegalStateException, SecurityException, SystemException; public abstract void setRollbackOnly() throws IllegalStateException, SystemException; public abstract void setTransactionTimeout(int seconds) throws SystemException; }
Here's what the methods defined in this interface do:
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.
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.
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.
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.
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.
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:
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.)
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.
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.
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.
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.
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.
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.
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.
A transaction is associated with the UserTransaction object; the transaction is in the process of rolling back.
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.
Copyright © 2001 O'Reilly & Associates. All rights reserved.