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:
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.
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.
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:
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
|
For the database, we use MySQL 4.0.12 and its JDBC driver (see Resources). By default, MySQL tables are not
transactional. To support transactions, the tables have to be created with the
InnoDB type. In addition, to enable InnoDB type, you have to comment the line #skip-innodb in the MySQL configuration file, my.cnf.
The example is configured for the MySQL user javauser with a
password of javadude. Make sure that this user is created and has
the correct rights to create databases.
A script to create databases and tables is included in the example file in the scripts/ directory. It will create the account table and insert two clients:
john_doe with $100 in his account.jane_doe with $600 in her account.Example 2. account table creation
mysql> CREATE DATABASE banktest;
mysql> USE banktest;
mysql> CREATE TABLE account(
-> client VARCHAR(25) NOT NULL PRIMARY KEY,
-> money INT) TYPE=InnoDB;
mysql> INSERT INTO account VALUES("john_doe", 100);
mysql> INSERT INTO account VALUES("jane_doe", 600);
mysql> SELECT * FROM account;
+----------+-------+
| client | money |
+----------+-------+
| john_doe | 100 |
| jane_doe | 600 |
+----------+-------+
The script will also create the atm table with $500 available cash.
Example 3. atm table creation
mysql> CREATE DATABASE atmtest;
mysql> USE atmtest;
mysql> CREATE TABLE atm(
-> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> cash INT) TYPE=InnoDB;
mysql> INSERT INTO atm VALUES(null, 500);
mysql> SELECT * FROM atm;
+----+------+
| id | cash |
+----+------+
| 1 | 500 |
+----+------+
Finally, you have to copy the JDBC driver .jar file in $CATALINA_HOME/shared/lib.
This article has been written for Tomcat 4.1.18 and above. Make sure you are not using an older version. There is nothing special to do when installing Tomcat, just download it and unzip it.
To use JOTM, you just have to download its latest binary release and unzip the file. Copy the .jar files (except log4j.jar, commons-cli.jar, and jotm_iiop_stubs.jar) from its lib/ directory to $CATALINA_HOME/shared/lib. That's all!
You now need to configure Tomcat to be able to get the
UserTransaction and DataSource objects (which
are used in foo.BankAccount and bar.ATM) from
JNDI.
First, tell Tomcat which JNDI names you'll use to look up your data sources
in your web application. This is done in web.xml file listed
below. For the Bank Account data source, the JNDI name used is
java:comp/env/jdbc/bankAccount, but you just have to give the name
after the java:comp/env/. Tomcat will resolve the rest through
the JNDI mechanism. The same thing has to be done for the ATM data source.
Example 4. web.xml
<web-app>
<resource-env-ref>
<description>Bank Account DataSource</description>
<resource-env-ref-name>jdbc/bankAccount</resource-env-ref-name>
<resource-env-ref-type>javax.sql.DataSource</resource-env-ref-type>
</resource-env-ref>
<resource-env-ref>
<description>ATM DataSource</description>
<resource-env-ref-name>jdbc/ATM</resource-env-ref-name>
<resource-env-ref-type>javax.sql.DataSource</resource-env-ref-type>
</resource-env-ref>
</web-app>
You now need to tell Tomcat how to retrieve the resources described in web.xml. This is done in the bank.xml file listed below. For both bank account and ATM resources, you must set parameters so that Tomcat can connect your web application to the data sources correctly. A more detailed description can be found in Tomcat JNDI DataSource How-to (see Resources).
One parameter is of specific interest: factory. The class set
by this parameter will be used to create a data source when the web application
looks it up through JNDI. Another important resource described in
web.xml is UserTransaction. This resource is used by
java:comp/UserTransaction to demarcate the transaction. The
implementation of this resource is provided by JOTM.
Example 5. bank.xml
<Context path="/bank" docBase="bank.war" debug="0" reloadable="true" crossContext="true">
<!-- Description of the DataSource "jdbc/bankAccount" -->
<Resource name="jdbc/bankAccount" auth="Container" type="javax.sql.DataSource" />
<ResourceParams name="jdbc/bankAccount">
<parameter>
<!-- Factory of the DataSource -->
<name>factory</name>
<value>org.objectweb.jndi.DataSourceFactory</value>
</parameter>
<parameter>
<name>url</name>
<value>jdbc:mysql://localhost/banktest</value>
</parameter>
<!-- other parameters include:
o username - name of database user
o password - password of the database user
o driverClassName - JDBC Driver name
-->
...
</ResourceParams>
<!-- Description of the DataSource "jdbc/ATM" -->
<Resource name="jdbc/ATM" auth="Container" type="javax.sql.DataSource" />
<!-- same type of parameters than for resource "jdbc/bankAccount" -->
<ResourceParams name="jdbc/ATM">
...
</ResourceParams>
<!-- Description of the resource "UserTransaction -->
<Resource name="UserTransaction" auth="Container" type="javax.transaction.UserTransaction" />
<ResourceParams name="UserTransaction">
<parameter>
<name>factory</name>
<value>org.objectweb.jotm.UserTransactionFactory</value>
</parameter>
<parameter>
<name>jotm.timeout</name>
<value>60</value>
</parameter>
</ResourceParams>
</Context>
Once you've setup JOTM and Tomcat, deploying and using the web application is easy. First, download the file bank.tgz and unzip it. Copy bank.xml and bank.war into $CATALINA_HOME/webapps. Next, start TOMCAT:
> cd $CATALINA_HOME/bin
> ./catalina.sh run
Using CATALINA_BASE: /home/jmesnil/lib/tomcat
Using CATALINA_HOME: /home/jmesnil/lib/tomcat
Using CATALINA_TMPDIR: /home/jmesnil/lib/tomcat/temp
Using JAVA_HOME: /usr/local/java
May 6, 2003 5:56:00 PM org.apache.commons.modeler.Registry loadRegistry
INFO: Loading registry information
May 6, 2003 5:56:00 PM org.apache.commons.modeler.Registry getRegistry
INFO: Creating new Registry instance
May 6, 2003 5:56:00 PM org.apache.commons.modeler.Registry getServer
INFO: Creating MBeanServer
May 6, 2003 5:56:07 PM org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on port 8080
Starting service Tomcat-Standalone
Apache Tomcat/4.1.24-LE-jdk14
You can notice in the logs that JOTM is not started yet. It will only be
started when you will access a DataSource for the first time. At
that time, you will have messages like:
May 6, 2003 5:56:20 PM org.objectweb.jotm.Jotm <init>
INFO: JOTM started with a local transaction factory that is not bound.
May 6, 2003 5:56:20 PM org.objectweb.jotm.Jotm <init>
INFO: CAROL initialization
Browse to the URL http://localhost:8080/bank/ to use the web application.
The home page of the web application contains two links:
1. To the Cash Delivery page, which you can use to simulate money withdrawal from an ATM.

Figure 2. Cash Delivery page
2. To the management console, which you can use to check or update information related to the ATM or to the several bank accounts you've created.

Figure 3. Management Console
Before any operations, there's $500 in the ATM. John Doe has $100 in his bank account and Jane Doe has $600 in hers.
If John Doe tries to withdraw $400, the transaction will fail, because there is not enough money in his bank account. The results will be:
Client ID: john_doe, value: $400
Cash can not be delivered to you
because: not enough money in your account (only $100).
If Jane Doe tries to withdraw $550, the transaction will fail, because there is not enough cash in ATM. The results will be:
Client ID: jane_doe, value: $550
Cash can not be delivered to you
because: not enough cash available from this ATM (only
$500).
If John Doe tries to withdraw $50, the transaction succeeds. The result will be:
Client ID: john_doe, value: $50
Please take your cash ($50)
Thank you!
This simple example demonstrates how servlets can provide robustness and simplicity by using transactions to ensure correct behavior in any case. Tomcat and JOTM combine well to take advantage of transactions in servlets in a lightweight way.
There is more to JOTM than what this simple example has shown. JOTM can help you to enhance your web application by providing:
Examples and documentation for all of these features can be found in JOTM distributions and on its web site.
The example code can be downloaded from this .tgz file. It includes the web application (bank.war) and its descriptor (bank.xml). Sources are also included, and a Ant build file is provided to compile and package the application. You will need jta-spec1_0_1.jar in your classpath to compile the example (the .jar is in the lib/ directory of the JOTM installation).
ONJava.com has published a series of articles about J2EE Transactions:
"J2EE Transaction Frameworks: Building the Framework"
"J2EE Transaction Frameworks: Distributed Transaction Primer"
"J2EE Transaction Frameworks, Part 3"
The ObjectWeb Consortium is an open-source-software community created at the end of 1999 whose goal is the development of open source, distributed middleware in the form of flexible and adaptable components.
The example uses MySQL 4.0.12 and its JDBC driver.
Tomcat 4.1.24 is the reference implementation for the Servlets 2.3 and JSP 1.2 specifications. Tomcat's documentation contains a how-to about DataSource JNDI configuration.
Jetty is another popular open source servlet container. As of version 4.2.10, Jetty integrates JOTM to provide out-of-the-box transaction support to servlets (see the JettyPlus page).
Jeff Mesnil is a Software Engineer at INRIA, where he works for the ObjectWeb Consortium on JOTM, an open source transaction manager in Java.
Return to ONJava.com.
Copyright © 2007 O'Reilly Media, Inc.