Explicit Transaction Management






Explicit Transaction Management

Although this section covers JTA, it is strongly recommended that you do not attempt to manage transactions explicitly. Through transaction attributes, Enterprise JavaBeans 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.


EJB provides implicit transaction management on the method level: 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.

Explicit management of transactions is normally accomplished using the OMG's Object Transaction Service (OTS) or the Java implementation of OTS, the Java Transaction Service (JTS). OTS and JTS provide APIs that allow developers to work with transaction managers and resources (e.g., databases and JMS providers) 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. 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 it is accessible to enterprise beans and is recommended for client applications. The low-level XA interface is used by the EJB server and container to coordinate transactions with resources such as databases.

Your use of explicit transaction management will probably focus on one simple interface: javax.transaction.UserTransaction .UserTransaction allows you to manage the scope of a transaction explicitly. Here's how explicit demarcation might be used in an EJB or client application:

TravelAgent tr1 = (TravelAgent)getInitialContext( ).lookup("TravelAgentRemote");
tr1.setCruiseID(cruiseID);
tr1.setCabinID(cabin_1);
tr1.setCustomer(customer0;
TravelAgent tr2 = (TravelAgent)getInitialContext( ).lookup("TravelAgentRemote");;
tr2.setCruiseID(cruiseID);
tr2.setCabinID(cabin_2);
tr2.setCustomer(customer);

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 does not want to book either cabin unless he can get both, so the client application is designed to include both bookings in the same transaction. This is accomplished by explicitly marking the transaction's boundaries through use of the javax.transaction.UserTransaction object. Each enterprise bean method invoked by the current thread between the UserTransaction.begin( ) and UserTransaction.commit( ) methods is included in the same transaction scope, according to the transaction attributes of the enterprise 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 example, may be a client application or another enterprise bean.[*]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 an enterprise bean.

[*] Only beans declared as managing their own transactions (bean-managed transaction beans) can use the UserTransaction interface.

Java Enterprise Edition (Java EE) specifies how a client application can obtain a UserTransaction object using JNDI. Here's how a client obtains a UserTransaction object if the EJB container is part of a Java EE system (Java EE and its relationship with EJB are covered in more detail in Chapter 18):

Context jndiCntx = new InitialContext( );
UserTransaction tran = (UserTransaction)
    jndiCntx.lookup("java:comp/UserTransaction");
utx.begin( );
...
utx.commit( );

Enterprise beans can also manage transactions explicitly. Only session beans and message-driven beans that define a javax.ejb.TransactionManagementType of Bean using the @javax.ejb.TransactionManager annotation can manage their own transactions. Enterprise beans that manage their own transactions are frequently referred to as bean-managed transaction (BMT) beans. Entity beans can never be BMT beans. BMT beans do not declare transaction attributes for their methods. Here's how a session bean declares that it will manage transactions explicitly:

import javax.ejb.* ;
import javax.annotation.* ;
import javax.transaction.UserTransaction;

@Stateless
@TransactionManagement(TransactionManagerType.BEAN)
public class HypotheticalBean implements HypotheticalLocal {
...
}

To manage its own transaction, an enterprise bean needs to obtain a UserTransaction object. An enterprise bean obtains a reference to the UserTransaction from the EJBContext or from @Resource injection:

import javax.ejb.* ;
import javax.annotation.* ;
import javax.transaction.UserTransaction;

@Stateless
@TransactionManagement(TransactionManagerType.BEAN)
public class HypotheticalBean implements HypotheticalLocal {
    @Resource 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) {...}

Alternatively, the UserTransaction can be injected directly into the bean:

import javax.ejb.* ;
import javax.annotation.* ;
import javax.transaction.UserTransaction;

@Stateless
@TransactionManagement(TransactionManagerType.BEAN)
public class HypotheticalBean implements HypotheticalLocal {
    @Resource UserTransaction ut;
...
}

Finally, an enterprise bean can also access the UserTransaction from the JNDI ENC. The enterprise bean performs the lookup using the java:comp/env/UserTransaction context:

InitialContext jndiCntx = new InitialContext( );
UserTransaction tran = (UserTransaction)
    jndiCntx.lookup("java:comp/env/UserTransaction");

Transaction Propagation in Bean-Managed Transactions

With stateless session beans, transactions that are managed using UserTransaction must be started and completed within the same method. In other words, UserTransaction transactions cannot be started in one method and ended in another. This makes sense because stateless session bean instances are shared across many clients; while one stateless instance may service a client's first request, a completely different instance may service a subsequent request by the same client. With stateful session beans, however, a transaction can begin in one method and be committed in another because a stateful session bean is used by only one client. Therefore, a stateful session bean can associate itself with a transaction across several different client-invoked methods. As an example, imagine the TravelAgent EJB as a BMT bean. In the following code, the transaction is started in the setCruiseID( ) method and completed in the bookPassage( ) method. This allows the TravelAgent EJB's methods to be associated with the same transaction. The definition of the travelAgentBean class looks like this:

import com.titan.reservation.*;

import javax.ejb.EJBException;

@Stateful
@TransactionManagement(TransactionManagerType.BEAN)
public class TravelAgentBean implements TravelAgentRemote {
    ...
    public void setCruiseID(Integer cruiseID) {
        try {
            ejbContext.getUserTransaction().begin( );
            cruise = entityManager.getReference(Cruise.class, cruiseID);
        } catch(Exception re) {
            throw new EJBException(re);
        }

    }
    public TicketDO bookPassage(CreditCardDO card, double price)
        throws IncompleteConversationalState {

        try {
            if (ejbContext.getUserTransaction().getStatus( ) !=
                javax.transaction.Status.STATUS_ACTIVE) {

                throw new EJBException("Transaction is not active");
            }

        } catch(javax.transaction.SystemException se) {
             throw new EJBException(se);
        }

        if (customer == null || cruise == null || cabin == null)
        {
            throw new IncompleteConversationalState( );
        }
        try {
            Reservation reservation =
                new Reservation(customer, cruise, cabin, price);

            process.byCredit(customer, card, price);

            TicketDO ticket = new TicketDO(customer,cruise,cabin,price);

            ejbContext.getUserTransaction().commit( );

            return ticket;
        } catch(Exception 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 is still active. If the transaction is no longer active, we throw an exception. The use of getStatus( ) is covered in more detail later in this chapter.

When a client that is already involved in a transaction invokes a bean-managed transaction method, the client's transaction is suspended until the method returns. This suspension occurs regardless of whether the BMT bean explicitly started its own transaction within the method or the transaction was started in a previous method invocation. The client transaction is always suspended until the BMT method returns.

Transaction control across methods is strongly discouraged because it can result in improperly managed transactions and long-lived transactions that lock up or even leak resources.


Message-driven beans and bean-managed transactions

Message-driven beans also have the option of managing their own transactions. In the case of MDBs, the scope of the transaction must begin and end within the onMessage( ) methodit is not possible for a bean-managed transaction to span onMessage( ) calls.

You can transform the ReservationProcessor EJB you created in Chapter 12 into a BMT bean simply by changing its javax.ejb.TransactionManagementType value to Bean:

@MessageDriven
@TransactionManagement(BEAN)
public class ReservationProcessorBean 
 implements
MessageListener {
...
}

In this case, the ReservationProcessorBean class would be modified to use javax.transaction.UserTransaction to mark the beginning and end of the transaction:

@MessageDriven
@TransactionManagement(BEAN)
public class ReservationProcessorBean implements javax.jms.MessageListener {
    @PersistenceContext(unitName="titanDB")
    private EntityManager em;

    @EJB
    private ProcessPaymentLocal process;

    @Resource(name="ConnectionFactory")
    private ConnectionFactory connectionFactory;

    @Resource UserTransaction ut;

    public void onMessage(Message message) {
        try {
         ut.begin( );

            MapMessage reservationMsg = (MapMessage)message;

            int customerPk = reservationMsg.getInt("CustomerID");
            int cruisePk = reservationMsg.getInt("CruiseID");
            int cabinPk = reservationMsg.getInt("CabinID");

            double price = reservationMsg.getDouble("Price");

            // get the credit card
            Date expirationDate =
                new Date(reservationMsg.getLong("CreditCardExpDate"));
            String cardNumber = reservationMsg.getString("CreditCardNum");
            String cardType = reservationMsg.getString("CreditCardType");
            CreditCardDO card = new CreditCardDO(cardNumber,
                expirationDate, cardType);
            Customer customer = em.getReference(Customer.class, customerPk);
            Cruise cruise = em.getReference(Cruise.class, cruisePk);
            Cabin cabin = em.getReference(Cabin.class, cabinPk);

            Reservation reservation = new Reservation(
                          customer, cruise, cabin, price, new Date( ));
            em.persist(reservation);

            process.byCredit(customer, card, price);

            TicketDO ticket = new TicketDO(customer,cruise,cabin,price);

            deliverTicket(reservationMsg, ticket);

         ut.commit( );

        } catch(Exception e) {
            throw new EJBException(e);
        }
    }

    ...

It is important to understand that in a BMT, the message consumed by the MDB is not part of the transaction. When an MDB uses container-managed transactions, the message it is handling is a part of the transaction, so if the transaction is rolled back, the consumption of the message is also rolled back, forcing the JMS provider to redeliver the message. But with bean-managed transactions, the message is not part of the transaction, so if the BMT is rolled back, the JMS provider will not be aware of the transaction's failure. However, all is not lost, because the JMS provider can still rely on message acknowledgment to determine if the message was successfully delivered.

The EJB container will acknowledge the message if the onMessage( ) method returns successfully. If, however, a RuntimeException is thrown by the onMessage( ) method, the container will not acknowledge the message and the JMS provider will suspect a problem and probably attempt to redeliver the message. If redelivery of a message is important when a transaction fails, your best course of action is to ensure that the onMessage( ) method throws an EJBException so that the container will not acknowledge the message received from the JMS provider.

Vendors use proprietary (declarative) mechanisms to specify the number of attempts to redeliver messages to BMT/NotSupported MDBs that "fail" to acknowledge receipt. The JMS-MDB provider may provide a "dead message" area into which such messages will be placed if they cannot be successfully processed according to the retry count. Administrators can monitor the dead message area so that delivered messages can be detected and handled manually.


Other than the message, everything between the UserTransaction.begin( ) and UserTransaction.commit( ) methods is part of the same transaction. This includes creating a new Reservation EJB and processing the credit card using the ProcessPayment EJB. If a transaction failure occurs, these operations will be rolled back. The transaction also includes the use of the JMS API in the deliverTicket( ) method to send the ticket message. If a transaction failure occurs, the ticket message will not be sent.

Heuristic Decisions

Transactions are normally controlled by a transaction manager (often the EJB server), which manages the ACID characteristics across several enterprise beans, databases, and servers. The 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, but basically it requires that servers and databases cooperate through an intermediarythe transaction managerin order to ensure that all of the data is made durable together. Some EJB servers support 2-PC and others do not, 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, JMS providers, and other resources), it decides whether all the updates should be committed or rolled back. A heuristic decision takes place when one of the resources makes a unilateral decision to commit or roll back without permission from the transaction manager. When a heuristic decision has been made, the atomicity of the transaction is lost and data-integrity errors can occur.

UserTransaction throws a few different exceptions related to heuristic decisions; these are discussed in the following section.

UserTransaction

EJB servers are required to support UserTransaction but are not required to support the rest of JTA, nor are they required to use JTS for their transaction service. 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:


begin( )

Invoking the begin( ) method creates a new transaction. The thread that executes the begin( ) method is immediately associated with the new transaction, which is then propagated to any EJB that supports existing transactions. The begin( ) method can throw one of two checked exceptions. An 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. A SystemException is thrown if the transaction manager (i.e., 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. An IllegalStateException is thrown if the current thread is not associated with a transaction. A SystemException is thrown if the transaction manager (the EJB server) encounters an unexpected error condition. A TRansactionRolled-backException is thrown when the entire transaction is rolled back rather than committed; this can happen if one of the resources was unable to perform an update or if the UserTransaction.rollBackOnly( ) method was called. A HeuristicRollbackException indicates that one or more resources made a heuristic decision to roll back the transaction. A HeuristicMixedException indicates resources made heuristic decisions to both roll back and commit the transaction; that is, some resources decided to roll back and 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. A SecurityException is thrown if the thread using the UserTransaction object is not allowed to roll back the transaction. An IllegalStateException is thrown if the current thread is not associated with a transaction. A SystemException is thrown if the transaction manager (the EJB server) encounters an unexpected error condition.


setRollbackOnly( )

The setRollbackOnly( ) method is invoked to mark the transaction for rollback. This means that, regardless of whether the updates executed within the transaction succeed, the transaction must be rolled back when completed. This method can be invoked by any BMT EJB participating in the transaction, or by the client application. The setRollBackOnly( ) method can throw one of two checked exceptions. An IllegalStateException is thrown if the current thread is not associated with a transaction; a SystemException is thrown if the transaction manager (the EJB server) encounters an unexpected error condition.


setTransactionTimeout(int seconds)

The setTransactionTimeout(int seconds) method sets the lifespan of a transaction, i.e., 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 0 seconds, the default timeout of the transaction manager will be used. This method must be invoked after the begin( ) method. A 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. A sophisticated programmer can use this method to determine the status of a transaction associated with a UserTransaction object. A SystemException is thrown if the transaction manager (EJB server) encounters an unexpected error condition.

Status

Status is a simple interface that contains constants but no methods. Its sole purpose is to provide a set of constants that describe the status of a transactional objectin this case, 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 two-phase 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 instead of 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.

EJBContext Rollback Methods

Only BMT beans can access UserTransaction from EJBContext and the JNDI ENC. Container-managed transaction (CMT) beans cannot use UserTransaction. CMT beans use the setRollbackOnly( ) and geTRollbackOnly( ) methods of EJBContext to interact with the current transaction instead. Later in this chapter, we'll see that exceptions can be used to roll back the transaction.

The setRollbackOnly( ) method gives an enterprise bean the power to veto a transaction, which can be used if the enterprise bean detects a condition that would cause inconsistent data to be committed when the transaction completes. Once an enterprise 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 information can be used to avoid executing work that would not be committed anyway. For example, if an exception is thrown and captured within an enterprise bean method, getrollbackOnly( ) 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 did not, the EJB has an opportunity to correct the problem and retry the task that failed. Only expert EJB developers should attempt to retry tasks within a transaction. Alternatively, if the exception did not cause a rollback (i.e., geTRollbackOnly( ) returns false), a rollback can be forced using the setRollbackOnly( ) method.

BMT beans must not use the setRollbackOnly( ) and getrollbackOnly( ) methods of the EJBContext . BMT beans should use the getStatus( ) and rollback( ) methods on the UserTransaction object to check for rollback and force a rollback, respectively.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows