Transaction handling in EJB

In database programming, often the concept of a “transaction” is used.  A transaction refers to one or more operations that are done in a tentative manner.  The programmer first states all operations to be done.  Then at the end, the programmer decides to commit to all the operations or to abort all of them.  If the programmer decides to commit to the operations, they are all done as one unit.  If the programmer aborts the operations, none of them leave any effect.  A second benefit of “transactions”, besides the ability to abort or commit, is that all operations work as a single unit.  So while you look up the price of something and before you buy it, you can make sure the price won’t change between the two operations by packaging the operations in a single transaction.

For instance, suppose the bank holding somebody’s account also handles the stock purchases for them.  Let us say the client wants the bank to purchase 400 shares of stock XYZ for $30.  First of all, the bank would want to make sure that while the stock purchase is in progress, some other ATM or teller doesn’t remove some money from the account, thereby causing the balance to fall below the required amount.  So the bank would package everything as a single transaction, including the check to see if enough balance is available, the purchase of the stocks and the deduction of the purchase price from the balance.  So that if the stocks are available and the “buy” operation suceeds, everything is ok, otherwise the original amount in the account would revert back.

As another example, when we implemented the “buy” method of the StockQuotes EJB, we needed multiple database queries.  What happens if two people try to buy the stocks at the same time, so that between the time one EJB instance has looked up the minimum price and the time the database is changed to reflect the purchase, the second person’s EJB instance gets in?  This can place some invalid results in the database.  By using a transaction, we can avoid such conflicts.

But we don’t have to write the transaction handling ourselves.  For this, we can use the built-in “transaction” features of EJBs.  EJBs have built-in support for transaction handling.  Moreover, transactions can be easily made to work over multiple EJBs, and even over multiple databases as well as other transaction-oriented resources.

The programmer works with JDBC as usual.  But in the deployment-descriptor, the transaction requirements are specified.  When it comes time to make the “commit/abort” decision, the EJB container checks with the client to give it an oportunity to abort the whole transaction.  If the client doesn’t decide to abort the operation, the EJB container proceeds with commiting the transaction.  Otherwise, any JDBC (and possibly other) operations the method has done, revert back to their original state.

To specify transaction requirements for a method, we need to specify a “transaction-attribute”.  The transaction-attribute has one of the following values:

NotSupported Transactions are not supported in this method.
Supports This method can be called as part of a transaction or independently.
Required This method needs to be part of a transaction.  If called outside a transaction, a new transaction is automatically started.
RequiresNew When this method is called, a new transaction should be started.
Mandatory This method can be called only as part of a transaction.
Never This method cannot be called as part of a transaction.

For our purposes, we need to mark our method “buy” as requiring a new transaction.

The transactions are marked as part of the <assembly-descriptor> tag.  Here is a set of XML nodes that specify that the method “buy” requires a transaction.  Note that the <assembly-descriptor> should go at the same level as the <enterprise-beans> node.

<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>StockQuotes</ejb-name>
<method-name>buy</method-name>
</method>
<trans-attribute>RequiresNew</trans-attribute>
</container-transaction>
</assembly-descriptor>

Add this tag in the deployment descriptor of the StockQuotes EJB, and this will make sure the “buy” method is always executed within the context of a transaction.  So we won’t get any surprises with the database changing between we looked up the minimum price and the time we updated the database to reflect our purchase.
Sometimes transactions need to be aborted.  Let us consider the bank-account EJB (from the previous exercises.)  Suppose our bank has decided to handle stock purchases for its clients. Then we might want to add a “buyStocks” method in the bank-account remote interface,
boolean buyStocks( String name, float price, int number );

The buyStocks method will also need to be marked with a transaction attribute of RequiresNew.  The implementation of this method will call the linked StockQuotes EJB to buy some stocks.  If the buy operation fails, it will tell the container to abort the transaction.

To tell the container to abort the transaction, we use the SessionSynchronization interface.

In your bank-account bean, add a boolean variable “failed”.  Add the implementation of “buyStocks” that sets “failed” to false, removes the money from the user’s account by doing a database UPDATE, then cals the StockQuotes ejb to buy the stocks.  If the stock purchase fails, it sets the “failed” variable to true.  (If the purchase succeeds, credit any unused amount back to the user’s account.)

Add “implements javax.ejb.SessionSynchronization” to the bean class definition.  Provide the methods from this interface, as shown below.  We are only interested in “beforeCompletion” which will be called just before returning from the method and before the transactions are committed.

public void afterBegin() throws  javax.ejb.EJBException,java.rmi.RemoteException

{

}
java.rmi.RemoteException

{
if ( failed ) {
ejbSessionContext.setRollbackOnly();
failed = false;
}
}
java.rmi.RemoteException
{
}
public void afterCompletion()  throws javax.ejb.EJBException

Note that if the purchase has failed, we mark the transaction as “rollback only”.  This wil cause all our JDBC changes to be rolled back instead of being committed.

If you wish to handle transactions yourself, just mark the bean’s <transaction-type> as “Bean” instead of “Container”.  Now the responsibility of starting transactions, and committing and aborting them is yours.  The EJB provides an interface javax.transaction.UserTransaction that you can retrieve by calling ejbSessionContext.getUserTransaction().  You can use this interface to start new transactions and to commit and rollback them while still working within the EJB framework to fit in with all other EJB operations.

Exercise:

1)  Change your bank-account EJB to provide “buyStocks” as specified above.  Test it.

2)  Transactions work with linked EJBs also!  Since we have the bank-account EJB and StockQuotes EJB linked, we can include the StockQuotes EJB’s buy method in the same transaction.
To make this happen, mark the “buy” method of the StockQuotes bean with transaction attribute “Requires” and rebuild and redeploy the bean.  Now this method can be called as before, but it can also be called as part of a transaction.  If it is part of an existing transaction (e.g. when is called from bank-account’s buyStocks method) it will continue to use that transaction instead of requiring a new one.

This can be used to implement a feature such as “don’t buy unless at least half of my required numbers of stocks is available.”  When deciding whether to commit or abort, even if the “buy” succeeded, check if the “buy” method was able to buy at least half the required number of stocks.  If not, abort the transaction.  Since the “buy” was included in the same transaction, it would get aborted too and the number of available stocks in the stocks database will not be affected.
This distributed transaction ability is what makes EJBs particularly useful in handling transactions.

3)  Add a “all or none” version of the “buyStocks” method.  In this case, the client either wants the entire amount of the order to be fulfilled, or doesn’t want any stocks at all.  E.g. if the client has ordered 400 shares and only 200 are available for the given price, the client doesn’t want to buy anything.
Implement this by doing multiple “buy”s until either the required number of stocks has been reached or the operation fails.  If not enough stocks are available, abort all actions until that point.  Test and confirm that everything is happening in a single transaction.