Tutorial Index Page
Transaction handling in EJBs
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. |
| Suports |
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
{
}
public void beforeCompletion() throws javax.ejb.EJBException,
java.rmi.RemoteException
{
if ( failed ) {
ejbSessionContext.setRollbackOnly();
failed = false;
}
}
public void afterCompletion throws javax.ejb.EJBException,
java.rmi.RemoteException
{
}
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.
|