Saturday, September 10, 2011

Spring and Declarative Transactions

A transaction is a unit of work that has ACID (atomic, consistent, isolated and durable) properties. Atomic means that the changes all happen or nothing happens. If money is debited from an account and credited to another account, a transaction ensures that either both the debit and credit complete or neither completes. Consistent implies that the changes leave the data in a consistent state. Isolated implies that changes do not interfere with other changes. Durable implies that once the changes are committed, they stay committed.

Resource managers such as relation databases provide a transaction manager and an API to control transactions. Those familiar with JDBC will know that by default a transaction is started because of the setting autocommit= true. Every statement that changes the database is automatically committed. This behavior can be changed by setting autocommit to false. Now the programmer must explicitly begin a transaction and then commit or rollback the transaction.

Transactions that deal with just one resource such as one database are known as local transactions. Transactions that span multiple resources such as more than one database or a database and a messaging engine are called global transactions. Global transaction are implemented using the XA protocol which involves a two phase commit. The JTA specification describes a java API for programmers to work with global transactions. The transaction methods in JDBC such as begin, commit, rollback work only with JDBC and relational databases, where as JTA can work with any transactional resource.

The code involved in working with transactions, however is boiler plate code that can be handled by a framework. At the start of the method, you need to begin a transaction and when the method completes, you need to either commit or rollback the transaction. If you have worked with EJBs, you might be familiar that you can specify in the deployment descriptor, the transactional environment in which the method should execute. For example you might say RequiresNew, which means start a new transaction before invoking the method. The container starts a new transaction before the method is invoked and commits it when the method returns. The programmer does not need to write any java code to handle transaction.

In rest of the article, we discuss with an example, declarative transaction management with Spring.

For this tutorial you will need:

(1) Spring 3.0
(2) Eclipse is optional. I use eclipse as my IDE. Eclipse lets you export the war that can be deployed to Tomcat. But you can use other IDEs or command line tools as well.
(3) You may download the source code for this example at springjdbcwithTransaction.zip .

We resuse the example from the JDBC with Spring blog we wrote some time ago. Let us add transactions support to MemberSpringJDBCDAO. This class has the insertMember method that inserts a member to the database. Let us modify the method a little bit to throw a RuntimeException after the insert into the database. The runtime exception is added to pretend that an error occured in business logic while updating the database.
public int insertMember(Member member) {
    JdbcTemplate jt = getJdbcTemplate() ;
    Object[] params = new Object[{member.getFirstname(),
        member.getLastname(),
        member.getStreet(),member.getCity(),
        member.getZip(),member.getEmail(),member.getPassword()} ;
  
    int ret = jt.update(insert_sql, params) ;
    throw new RuntimeException("simulate Error condition') ;
    return ret ;
}

In this method, would you expect the insert to be committed to the database ? The answer is Yes, though that is not the desirable behavior. The default behaviour of JDBC is autocommit = true , which means, each insert or update is committed immediately. You could set autocommit = false and explicitly commit or rollback at the end of the method. But it is much easier to let your container handle this.

To add declarative transaction management to the above method

Step 1: Define a transaction manager in springjdbcdao.xml

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/>


Spring works with the transaction manager to begin and complete transactions.

Step 2: Turn on support for transaction annotations

Add to springjdbcdao.xml

<tx:annotation-driven transaction-manager="txManager"/>


Step 3: Add the @Transactional annotation to the insertMember method

@Transactional
public int insertMember(Member member) {

@Transactional can take properties but we will go with default values which are:

Propagation : Required

Required means a transaction is required. If there is no transaction, Spring requests the transaction manager to start one. The other possible values is Requires_New, which tells the transaction manager to always suspend the existing transaction and start a new one.

Isolation level : Default

Use the default isolation level of the underlying resource manager.

Rollback : Any runtime exception triggers a rollback

Step 4: Run the updated insertMember method using Junit test MemberSpringJDBCDAOTest.

You will see the following logs from the transaction manager indicating the transaction rolled back.

org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction rollback
2501 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction rollback
2501 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection [org.apache.derby.impl.jdbc.EmbedConnection40@13320911 (XID = 2827), (SESSIONID = 1), (DATABASE = c:\manoj\mjprojects\database\pumausers), (DRDAID = null) ]
2501 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection [org.apache.derby.impl.jdbc.EmbedConnection40@13320911 (XID = 2827), (SESSIONID = 1), (DATABASE = c:\manoj\mjprojects\database\pumausers), (DRDAID = null) ]
2511

Use SQL to check the database table. Confirm that no record is added.

Step 5: Remove the runtimeexception from the insertMember method and run the test again.

The Spring debug log with show that the transaction is committed. Use SQL to check the database table. Confirm that a record is added to the table.

In summary, Transactions are necessary to maintain ACID properties for data sources. Declarative transactions using Spring makes that task easier.