Transaction Management in Java EE Environments - A Comparative Analysis

 

Purpose

This article describes various approaches to manage transactions for Java/JEE/open source technologies based applications. It also tabulates pros and cons of each.

Overview

A transaction is a logical unit of work comprising of several tasks; all or none of which must be performed in order to preserve data integrity. For example: A balance transfer from one bank account to another account is a transaction with two interdependent tasks viz. debit from one account and credit to another account.

begin transaction

debit account1 100

credit account2 100

commit transaction

For the transaction to be complete both the tasks must succeed.

Demarcation of a transaction or the transaction boundary restricts the tasks that need to be a part of one logical unit of work and therefore, need to be executed in a transaction. Primarily there are two ways to manage transactions either demarcate transactional boundaries programmatically in the code itself along with implementing the business logic or define it declaratively for the Container or Application Server to take care of it.

Programmatic Transaction Demarcation

As per this approach developers programmatically begin, commit, or rollback transactions in the code using any of the following APIs:

Transaction with JDBC Example:

 

Connection connection = null;

Statement stmt = null;

PreparedStatement pstmt = null;

ResultSet rs = null;

try {

      connection = getConnection();

      connection .setAutoCommit(false);

 

      stmt = connection.createStatement();

      rs = stmt.executeQuery(“some SELECT query”);

      // do something with result set.

      processResultSet(rs);

 

// perform an update operation

pstmt = connection.prepareStatement(“some UPDATE query”);

      pstmt.executeUpdate();

 

// commit the transaction

connection.commit();

 

} catch (SQLException ex) {

// rollback the transaction

try {
        connection.rollback();
      } catch (SQLException error) {
      }

// log error.

} finally {

// close all resources such as rs, stmt, pstmt and connection.

}

 

JTA Example:

try {

    UserTransaction tx = (UserTransaction)new InitialContext()

                            .lookup("java:comp/UserTransaction");

    tx.begin();

    // Do some work

    load(...);

    persist(...);

    // . . . etc.

    tx.commit();

}

catch (RuntimeException e) {

    tx.rollback();

    throw e; // or display error message

}

 

A code example depicting programmatic transaction demarcation with Hibernate Transaction API:

Session session = null;

Transaction tx = null;

try {

session = sessionFactory.openSession();

tx = session.beginTransaction();

// perform business logic here

doSomething(session);

tx.commit();

} catch (RuntimeException ex) {

try {

tx.rollback();

} catch (RuntimeException rEx) {

log.error("Couldn't roll back transaction", rEx);

}

throw ex;

} finally {

session.close();

}

The first approach is generally recommended. The second approach is similar to using the JTA UserTransaction API (although exception handling is less cumbersome).

Declarative Transaction Demarcation

Declaratively create a transaction assembly for example with annotation on methods. It’s then the responsibility of the application deployer and the runtime environment to handle this concern. This can be done in two ways:

Declarative Example:

@Stateless

public class ManageAccountBean implements ManageAccount {

 

@TransactionAttribute(TransactionAttributeType.REQUIRED)

public void updateAccount(Account account)throws AccountNotValidException {

 

// Merge account

account = getSessionFactory().getCurrentSession().update(account);

// do other stuff like notify account owner

...

}

...

}

When the method updateAccount() gets called, it executes with in a transactional context. At the end of execution of the method transaction gets committed or gets rolled back if any RuntimeException occur.

Types of Transaction Attributes

Six transactional attributes are possible for container-managed transaction demarcation.

 

Required: If there is no transaction, the container creates one; if there is one, it goes through. Upon returning, the container commits any transaction it started.

 

RequiresNew: The container always creates a new transaction, it always commits it.

 

Mandatory: If there is a transaction, the container propagates it. If not, the container throws an exception.

 

NotSupported: If a transaction is associated with the incoming call, the container stops the association and restarts the association upon leaving. If there is no transaction, the container does nothing.

 

Supports: If there is a transaction, the container propagates it. If not, the container does nothing.

 

Never: If there is a transaction the container throws an exception. If not, the container does nothing

Pros and Cons of programmatic versus Declarative Transaction Demarcation

Tables below compare various approaches of Transaction Management discussed above:

Table A: Comparison of Programmatic Vs Declarative Transaction Demarcation

Topic

Pros

Cons

Comments

Programmatic Transaction Demarcation

  • Extreme flexibility in the hands of programmer
  • Based on certain conditions being met at run time Transaction isolation level can be changed for individual transactions
  • Extra coding required makes it difficult to implement
  • Transaction concerns are spread around business logic (no separation of concern)
  • Exception handling and rollback is programmer’s responsibility
  • Releasing resources at the end of every transaction is programmer’s responsibility.
  • More exposed to the limitations of the database under the JDBC connection.

Programmatic transaction management is usually a good idea only if we have a small number of transactional operations. For example, if we have a web application that require transactions only for certain update operations, we may not want to set up transactional proxies using Spring or any other technology.

Declarative Transaction Demarcation

  • Transaction demarcations can be easily configured using annotations or xml files.
  • Any RuntimeException triggers rollback and other checked exceptions can also be marked for rollback if required.
  • Separation of concern is achieved and business logic is not littered with transaction management specific code.
  • It is the option with the least impact on application code, and hence is most consistent with the ideals of a non-invasive lightweight container.
  • Reduced flexibility such as Transaction isolation level can be set only per database schema or at the most at the method level if spring framework is used.

If application has numerous transactional operations, declarative transaction management is usually worthwhile. It keeps transaction management out of business logic, and is not difficult to configure.

 

Table B: Comparison of different APIs to achieve Programmatic Transaction Demarcation

Topic

Pros

Cons

Comments

Plain JDBC API

  • Easy to setup and supported by all the RDBMS vendors.
  • It binds application to plain JDBC environment. This makes it non portable.
  • It does not provide advanced transactional features such as enlisting multiple resources with in a transaction.
  • Transaction management is inefficient as compared to other APIs.
  • Real monitored transactions are not available.

It can be used for quick prototyping but is not recommended for production code.

Standard JTA UserTransaction API

  • The transaction management service unifies all the resources, no matter of what type, and exposes transaction control to the programmer with a single standardized API.
  • Multiple resources can be enlisted in a single transaction (i.e. supports two phase commit).
  • The quality of JTA implementations is higher compared to simple JDBC connection pools.
  • Needs JTA service of Java EE such as that provided by Java EE application servers.

 

Hibernate Transaction API

  • Programmer can use Hibernate Transaction API and underneath configure it to obtain Transaction management through JTA provider or plain JDBC. Thus Hibernate transaction API guarantees portability with a simple change of hibernate configuration.
  • Learning curve is shorter as programmer has to learn only hibernate API.
  • Managing transaction across multiple resources leads to cumbersome code unless certain advanced options of hibernate are used.

 

JPA EntityTransaction API

  • Provides standardized interface to perform bean managed transactions in Java SE environment which uses Java Persistence.
  • Application can be ported to Java EE environment which complies with Java Persistence.
  • API is limited as compared to JTA and hibernate.
  • It mainly deals with persistence to database and hence supporting other resources (such as a JMS resource) within the same transaction can be a challenge.

 

Spring framework

  • Provides a consistent programming model across different transaction APIs such as JTA, JDBC, Hibernate, iBATIS Database Layer and JDO.
  • Provides a simpler, easier to use, API for programmatic transaction management than most of these transaction APIs
  • Integrates with the Spring data access abstraction
  • One extra layer of configuration is required.
  • Dependency on Spring Transaction API which is not standardized.
  • Learning curve is involved.

 

 

Table C: Comparison of different ways of achieving Declarative Transaction Demarcation

Topic

Pros

Cons

Comments

EJB Container

  • Container takes care of the Transaction Management concerns.
  • The CMT code looks much nicer than programmatic transaction demarcation.
  • Chances of programming errors are greatly reduced.
  • Easy Context propagation (EJB A calls EJB B)
  • Rollback occur by default if any RuntimeException occurs, however even checked exceptions can be marked with annotation @ApplicationException(rollback=true).
  • Reduced flexibility
  • Need to encapsulate transactions in EJB components such as session beans.

This is most widely used approach for applications deployed on Java EE server.

Spring framework

  • Unlike EJB CMT, which is tied to JTA, Spring declarative transaction management works in any environment. It can work with JDBC, JDO, Hibernate or other transactions under the covers, with configuration changes only.
  • Spring enables declarative transaction management to be applied to any POJO, not just special classes such as EJBs.
  • Higher flexibility. For example, Spring offers declarative rollback rules: a feature with no EJB equivalent. Rollback can be controlled declaratively, not merely programmatically.
  • Spring allows customization of transactional behavior, using AOP. For example, if you want to insert custom behavior in the case of transaction rollback, you can. You can also add arbitrary advice, along with the transactional advice. With EJB CMT, you have no way to influence the container's transaction management other than setRollbackOnly().
  • As per the benchmarks of Spring developers, the performance of Spring declarative transaction management exceeds that of EJB CMT.
  • Spring does not support propagation of transaction contexts across remote calls, as do high-end application servers. If we need this feature, then we should use EJB.
  • Learning curve associated with Spring framework as its xml configuration or annotation syntax is different than that for EJB CMT. Also Spring syntax is not an open industry standard.
  • Not a standardized interface and locks the developer into Spring.

This approach is recommended if we want to have the flexibility of deploying the application with or without Java EE server.

 


Controlling Concurrent Access

Most applications require execution of concurrent transactions. For example in a joint account husband and wife both can potentially update the balance at the same time. Applications inherit the isolation guarantees provided by the database management system. For example, Hibernate never locks anything in memory. On the other hand, Hibernate and Java Persistence features for pessimistic and optimistic concurrency control at the application level, can improve the isolation guarantee beyond what is provided by the database.

Transaction Isolation Levels

The standard isolation levels are defined by the ANSI SQL standard and JTA conforms to them. Increased level of isolation has higher cost and causes serious degradation of performance and scalability. Where as decreased level of isolation can cause data integrity issues such as dirty reads and data loss. The different isolation levels are:

 

Pessimistic Locking

Pessimistic locking is an approach where an entity is locked in the database for the entire time that it is in application memory (such as an Order object of an application).  A lock either limits or prevents other users from working with the entity in the database.  A write lock indicates that the holder of the lock intends to update the entity and disallows anyone from reading, updating, or deleting the entity.  A read lock indicates that the holder of the lock does not want the entity to change while holding the lock, allowing others to read the entity but not update or delete it.  The scope of a lock might be the entire database, a table, a collection of rows, or a single row.  These types of locks are called database locks, table locks, page locks, and row locks respectively. 

The advantages of pessimistic locking are that it is easy to implement and guarantees that your changes to the database are made consistently and safely.  The primary disadvantage is that this approach isn’t scalable.  When a system has many users, or when the transactions involve a greater number of entities, or when transactions are long lived, then the chance of having to wait for a lock to be released increases.  Therefore this limits the practical number of simultaneous users that a system can support.

 

Optimistic Locking

With multi-user systems it is quite common to be in a situation where collisions are infrequent.  For example the two of the customers are working with Order objects, one is working with the object O1 for item I1 while the other is working with the object O2 for item I2 and therefore they won’t collide (Unless of course, if the database is explicitly set to do page level locking).  When this is the case optimistic locking becomes a viable concurrency control strategy.  The idea is that you accept the fact that collisions occur infrequently, and instead of trying to prevent them you simply choose to detect them and then resolve the collision when it does occur.

Figure and text below describes the logic for updating an object when optimistic locking is used:

Choosing Isolation level and locking mechanism

Assuming hibernate will be used for O-R mapping and persistence, let’s determine which isolation level and locking mechanism can serve best for most of the applications. We can easily eliminate read uncommitted and serializable isolation levels. Former is extremely dangerous as it allows one transaction’s uncommitted changes in a different transaction. Latter tends to scale very poorly and for most of the applications we do want concurrent transactions to occur as multiple concurrent users are expected to operate the system.

Now Hibernate can easily be configured to use versioned data. The combination of the (mandatory) persistence context cache and versioning (which allows us to resolve collision while using optimistic locking) already gives us most of the nice features of repeatable read isolation. In particular, versioning prevents the second lost updates problem, and the persistence context cache also ensures that the state of the persistent instance loaded by one transaction is isolated from changes made by other transaction. So, read committed isolation level for all database transaction can be acceptable if we use versioned data (optimistic locking). At the same time we can obtain a repeatable read guarantee explicitly in Hibernate for a particular transaction and piece of data with a pessimistic lock (such as using LockMode.UPGRADE which results in SQL “SELECT … FOR UPDATE” or similar)

 

Table D: Locking and Isolation Level suggestions for an online trading application

Table Type

Examples

Suggested Locking Strategy

Isolation Level

Live-High Volume

Order

Item

Optimistic (first choice)

Pessimistic (second choice)

Read Committed

Live-Low Volume

User

Pessimistic (first choice)

Optimistic (second choice)

Read Committed

Log (typically append only)

Access_Log

OrderHistory

UserHistory

Optimistic

Read Committed

Lookup/Reference (typically read only)

State (as in 50 states of USA)

Optimistic

Read Committed

 

Transactional Boundary

For a distributed system consisting of multiple remote clients connected to a central server we can potentially start a transactional context at the client and propagate it to the server (remote transaction propagation). Or create a service layer / business layer on the server side where all the transactions start and end. Let’s look at the pros and cons to help decide which can be used effectively for such an application.

 

 

Table A: Comparison of starting transactions from remote client vs. from local server

Topic

Pros

Cons

Comments

Transaction boundary at client side

  • Encompasses entire request/response coming from client.
  • Extremely reduced chance of data inconsistency between client and server.
  • Presentation layer at client has to deal with multiple concerns as transactional aspect is present along with view logic.
  • Requires transactional contexts to be propagated across remote calls.
  • Spring does not support remote propagation of transactions so we have to use an EJB container which supports it.
  • Involves more overhead leading to slower performance.

 

Transaction boundary on server side

  • No need of remote transaction propagation thus leading to higher flexibility (in choice of frameworks to be used) and performance.
  • Central place to manage the transactions thus leading to cleaner and more maintainable code.
  • We should carefully plan if data is to be cached at the client side
  • Can lead to data inconsistency if client has to update data while not connected to the server or loses connectivity in the middle of the transaction.

It is highly recommended by experts that Transaction demarcation and exception handling should be centralized.

 

One Possible Configuration

Assuming a distributed n-tier application which needs to support concurrent users and uses Spring/Hibernate:

  1. Use Spring Framework Declarative Transaction Demarcation or EJB3 container managed transactions with annotations.
  2. Underneath Spring Framework or EJB3 container use JPA/Hibernate and JTA service provided by the Transaction Manager or the one bundled in an application server such as JBoss.
  3. Use JTA connection pooling (almost all of the application servers has one).
  4. Define Read Committed database isolation level.
  5. Use optimistic locking for most of the tables and pessimistic locking only for those tables where Repeatable Read isolation level is required.
  6. Start and end transactional boundaries at server side.

Acknoledgements

This article compiles ideas from various soruces such as other web articles on Transactions and books on Java, J2EE, Hibernate, etc.