8.5. Explicit Transaction Management
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.
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.
... 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".
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"); 8.5.1. Transaction Propagation in Bean-Managed TransactionsWith 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.
8.5.2. Heuristic DecisionsTransactions 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. 8.5.3. UserTransactionUserTransaction 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:
8.5.4. StatusStatus 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:
8.5.5. EJBContext Rollback MethodsOnly 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. |
|