Adding Transactions to Servlets with JOTM
by Jeff Mesnil
07/30/2003
Introduction
J2EE provides a great deal of functionality besides servlets. Servlet
developers may be reluctant to use such features, neither willing nor having
time to replace their simple servlet containers with a big J2EE server that
provides more than what they need. However, given the modular nature of J2EE,
it is possible to enhance web applications by integrating small components
responsible for specific J2EE features into servlet containers. One such
feature is transactions.
For a complete description of J2EE transactions, you should have a look at
the three ONJava articles about it. For now, just
keep in mind that a transaction is a sequence of operations on resources (for
example, a database) defined by four properties, often referred to as
ACID due to their initials:
- Atomicity
- The transaction operations either all complete successfully (and the
transaction is committed), or none complete (and the transaction is
rolled back). This is the all-or-nothing property. A transaction
should be treated as a single unit of work. There is absolutely no possibility
of a mix of complete and incomplete operations in a transaction.
- Consistency
- A completed transaction transforms resources from one valid state to
another valid state. Some of the concrete examples of consistency are
referential integrity of the database and unique primary keys in tables.
- Isolation
- Changes to shared resources affected by a transaction do not become visible
outside of the transaction until the transaction commits. Isolation ensures that
transactions do not access data that is being updated concurrently.
- Durability
- The changes that result from a transaction commitment survive subsequent
system or media failures.
JOTM (Java Open Transaction Manager) is a fully functional, open source
standalone transaction manager developed by the ObjectWeb consortium. It provides transaction support for Java applications and is compliant with JTA, the Java Transaction API. You can find out more details on the JOTM home page. Integrating JOTM in
Tomcat (or any servlet container) makes it possible for JSP and servlet
developers to take advantage of transactions in a lightweight way to create
more robust web applications.
To highlight how transactions can enhance web applications, consider the
classical scenario of an ATM that uses a web browser to interact with the
client.
ATM Example
Scenario
The use case is simple: a client wants to withdraw money from an ATM. He
gives his client name, john_doe, and the amount of money he wants
to withdraw, $50. If he has enough money on his bank account and
if there is enough available cash in the ATM, the application gives him the
cash and withdraws the amount from his bank account. Otherwise, the operation
aborts and nothing happens except an error message. To focus on transactions
and to keep things simple, we will not worry about security issues, instead
assuming that the user is correctly authenticated.
This very simple example is surprisingly hard to implement in a robust way
without transactions. One client operation involves two different resources:
the ATM and the client's bank account. This automatically introduces ACIDity
issues in the application design. For example, if the operation succeeds on
the ATM and fails on the bank account (perhaps due to a communication failure),
the client will have the cash but his account won't be updated. Bad news for
the bank.
Even worse, if the bank account is correctly updated but an error prevents
the ATM from delivering the money, the client won't have the cash, but the money
will be withdrawn from his account.
To prevent such cases, in your application, you can 1) contact the two
resources and inform them about all of the current operations performed by the
client, 2) ask them if they can perform the operations, and, 3) if they both
agree, ask them to perform the operations. Even this solution is not robust
enough, however, if money is withdrawn from the client's bank account by
another operation between the second and third steps. It is possible that the
money withdrawal fails, for example, if the client can't have a negative
balance.
That's where transactions can make your application more simple and
robust: by performing all of the operations on the two resources within one
transaction, they will solve the ACIDity issues (especially Atomicity) for
you.
Application Design
Data Layer
At the data layer, we have two different databases and one table in each
database. To make the example more realistic, we've used two different
databases because it is possible to withdraw from an ATM not owned by the
client's bank (see below to configure the
databases).
banktest contains the account table, representing
client accounts.
atmtest contains the atm table, representing the
ATM.
Logic Layer
At the logic layer, we have three classes accessing the resources and
performing operations on them:
foo.BankAccount represents the bank account of a given client
and performs database operations on the account table through
JDBC.
bar.ATM represents the ATM and performs the JDBC operations on
the atm table.
bar.CashDelivery uses the two previous classes to perform a
client operation.
All of the logic is done in the deliverCash method of
CashDelivery.java.
The javax.transaction.UserTransaction interface is used to
demarcate the transaction. All of the operations between utx.begin()
and utx.commit() (or utx.rollback()) are executed
within one transaction. This ensures that your web application won't suffer of
the shortcomings discussed previously.
Thanks to transactions, the application logic is much simpler, and
consists of these simple steps:
- Start a transaction.
- Contact the bank of the client and withdraw money from his account.
- Tell the ATM to deliver the cash.
- Complete the transaction.
- If things succeeded, commit the transaction.
- Otherwise, rollback the transaction.
- Report to the client the outcome of the transaction. If the transaction
succeeds, cash will be delivered and money will be withdrawn from the account.
Otherwise, nothing happens.
Example 1. CashDelivery.java
public boolean deliver(String client, int value) {
InitialContext ctx = new InitialContext();
UserTransaction utx = (UserTransaction)
ctx.lookup("java:comp/UserTransaction");
...
boolean success = false;
try {
// begin the transaction
utx.begin();
// contact the bank of the client...
BankAccount account = new BankAccount(client);
// ... and withdraw the value from his account.
account.withdraw(value);
// contact the ATM...
ATM atm = new ATM();
// ... and deliver the cash to the client.
atm.deliverCash(value);
// everything went ok.
success = true;
} catch (Exception e) {
// something went wrong, we have to
// report it to the client
explanation += e.getMessage();
} finally {
try {
if (success) {
/* everything was ok, we commit the transaction.
* only now, the money will be effectively withdrawn
* from the account and the cash delivered to the client.
*/
utx.commit();
} else {
/* something went wrong, we rollback the transaction.
* none of the operations done within the transaction
* have been done.
*/
utx.rollback();
}
} catch (Exception e) {
/* something went wrong during the completion of the transaction.
* we're still guaranteed that none of the operations done within
* the transaction have been done/
*/
// we have to report it to the client
explanation += "\n" + e.getMessage();
// finally, the transaction was not a success
success = false;
} finally {
return success;
}
}
}
Presentation Layer
At the presentation layer, the application is composed of two JSP files:
atm.jsp, the Cash Delivery application, which sends to the
bar.CashDelivery class the client's login and the amount of cash
to withdraw, and displays the result of the client's operation.
admin.jsp, the Management Console used to show and update
information related to the two resources. (It is not really a part of the
application design, but has been added to simplify resource updates, such as to
deposit money on a client's account.)

Figure 1. Application Design