Feb. 5, 2011, 12:37 p.m.
posted by hashspark
Explicit Transaction Management
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.
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 TransactionsWith 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.
Message-driven beans and bean-managed transactionsMessage-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. 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 DecisionsTransactions 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. UserTransactionEJB 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:
StatusStatus 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:
EJBContext Rollback MethodsOnly 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. |
- Comment
