Chapter 16. Transaction Management in Spring

In this chapter, you will learn about the basic concept of transactions and Spring's capabilities in the area of transaction management. Transaction management is an essential technique in enterprise applications to ensure data integrity and consistency. Spring, as an enterprise application framework, provides an abstract layer on top of different transaction management APIs. As an application developer, you can use Spring's transaction management facilities without having to know much about the underlying transaction management APIs.

Like the bean-managed transaction (BMT) and container-managed transaction (CMT) approaches in EJB, Spring supports both programmatic and declarative transaction management. The aim of Spring's transaction support is to provide an alternative to EJB transactions by adding transaction capabilities to POJOs.

Programmatic transaction management is achieved by embedding transaction management code in your business methods to control the commit and rollback of transactions. You usually commit a transaction if a method completes normally and roll back a transaction if a method throws certain types of exceptions. With programmatic transaction management, you can define your own rules to commit and roll back transactions.

However, when managing transactions programmatically, you have to include transaction management code in each transactional operation. As a result, the boilerplate transaction code is repeated in each of these operations. Moreover, it's hard for you to enable and disable transaction management for different applications. If you have a solid understanding of AOP, you may already have noticed that transaction management is a kind of crosscutting concern.

Declarative transaction management is preferable to programmatic transaction management in most cases. It's achieved by separating transaction management code from your business methods via declarations. Transaction management, as a kind of crosscutting concern, can be modularized with the AOP approach. Spring supports declarative transaction management through the Spring AOP framework. This can help you to enable transactions for your applications more easily and define a consistent transaction policy. Declarative transaction management is less flexible than programmatic transaction management. Programmatic transaction management allows you to control transactions through your code—explicitly starting, committing, and joining them as you see fit. You can specify a set of transaction attributes to define your transactions at a fine level of granularity. The transaction attributes supported by Spring include the propagation behavior, isolation level, rollback rules, transaction timeout, and whether or not the transaction is read-only. These attributes allow you to further customize the behavior of your transactions.

Upon finishing this chapter, you will be able to apply different transaction management strategies in your application. Moreover, you will be familiar with different transaction attributes to finely define your transactions.

Programmatic transaction management is a good idea in certain cases where you don't feel the addition of Spring proxies is worth the trouble or negligible performance loss. Here, you might access the native transaction yourself and control the transaction manually. A more convenient option that avoids the overhead of Spring proxies is the TransactionTemplate class, which provides a template method around which a transactional boundary is started and then committed.

Problems with Transaction Management

Transaction management is an essential technique in enterprise application development to ensure data integrity and consistency. Without transaction management, your data and resources may be corrupted and left in an inconsistent state. Transaction management is particularly important for recovering from unexpected errors in a concurrent and distributed environment.

In simple words, a transaction is a series of actions that are treated as a single unit of work. These actions should either complete entirely or take no effect at all. If all the actions go well, the transaction should be committed permanently. In contrast, if any of them goes wrong, the transaction should be rolled back to the initial state as if nothing had happened.

The concept of transactions can be described with four key properties: atomicity, consistency, isolation, and durability (ACID).

  • Atomicity: A transaction is an atomic operation that consists of a series of actions. The atomicity of a transaction ensures that the actions either complete entirely or take no effect at all.

  • Consistency: Once all actions of a transaction have completed, the transaction is committed. Then your data and resources will be in a consistent state that conforms to business rules.

  • Isolation: Because there may be many transactions processing with the same data set at the same time, each transaction should be isolated from others to prevent data corruption.

  • Durability: Once a transaction has completed, its result should be durable to survive any system failure (imagine if the power to your machine was cut right in the middle of a transaction's commit). Usually, the result of a transaction is written to persistent storage.

To understand the importance of transaction management, let's begin with an example about purchasing books from an online bookshop. First, you have to create a new schema for this application in your database. If you are choosing Apache Derby as your database engine, you can connect to it with the JDBC properties shown in Table 16-1. For the examples in this book, we're using Derby 10.4.2.0.

Table 16.1. JDBC Properties for Connecting to the Application Database

Property

Value

Driver class

org.apache.derby.jdbc.ClientDriver

URL

jdbc:derby://localhost:1527/bookshop;create=true

Username

App

Password

App

With the preceding configuration, the database will be created for you because of the parameter on the JDBC URL: create=true. For your bookshop application, you need a place to store the data. You'll create a simple database to manage books and accounts.

The entity relational (ER) diagram for the tables looks like Figure 16-1.

BOOK_STOCK describes how many given BOOKs exist.

Figure 16.1. BOOK_STOCK describes how many given BOOKs exist.

Now, let's create the SQL for the preceding model. You'll use the ij tool that ships with Derby. On a command line, proceed to the directory where Derby is installed (usually just where you unzipped it when you downloaded it.). Descend to the bin directory. If Derby's not already started, run startNetworkServer (or startNetworkServer.bat on Windows). Now, you need to log in and execute the SQL DDL. Background the Derby Server process or open up a second shell and return to the same bin directory in the Derby installation directory. Execute ij. In the shell, execute the following:

connect 'jdbc:derby://localhost:1527/bookshop;create=true' ;

Paste the following SQL into the shell and verify its success:

CREATE TABLE BOOK (
    ISBN         VARCHAR(50)    NOT NULL,
    BOOK_NAME     VARCHAR(100)    NOT NULL,
    PRICE         INT,
    PRIMARY KEY (ISBN)
);

CREATE TABLE BOOK_STOCK (
    ISBN     VARCHAR(50)    NOT NULL,
    STOCK     INT            NOT NULL,
    PRIMARY KEY (ISBN),
    CHECK (STOCK >= 0)
);

CREATE TABLE ACCOUNT (
    USERNAME    VARCHAR(50)       NOT NULL,
    BALANCE        INT               NOT NULL,
    PRIMARY KEY (USERNAME),
    CHECK (BALANCE >= 0)
);

A real-world application of this type would probably feature a price field with a decimal type, but using an int makes the programming simpler to follow, so leave it as an int.

The BOOK table stores basic book information such as the name and price, with the book ISBN as the primary key. The BOOK_STOCK table keeps track of each book's stock. The stock value is restricted by a CHECK constraint to be a positive number. Although the CHECK constraint type is defined in SQL-99, not all database engines support it. At the time of this writing, this limitation is mainly true of MySQL because Sybase, Derby, HSQL, Oracle, DB2, SQL Server, Access, PostgreSQL, and FireBird all support it. If your database engine doesn't support CHECK constraints, please consult its documentation for similar constraint support. Finally, the ACCOUNT table stores customer accounts and their balances. Again, the balance is restricted to be positive.

The operations of your bookshop are defined in the following BookShop interface. For now, there is only one operation: purchase().

package com.apress.springrecipes.bookshop.spring;

public interface BookShop {

    public void purchase(String isbn, String username);
}

Because you will implement this interface with JDBC, you create the following JdbcBookShop class. To better understand the nature of transactions, let's implement this class without the help of Spring's JDBC support.

package com.apress.springrecipes.bookshop.spring;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

public class JdbcBookShop implements BookShop {

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void purchase(String isbn, String username) {
        Connection conn = null;
        try {
            conn = dataSource.getConnection();

            PreparedStatement stmt1 = conn.prepareStatement(
                    "SELECT PRICE FROM BOOK WHERE ISBN = ?");
            stmt1.setString(1, isbn);
            ResultSet rs = stmt1.executeQuery();
            rs.next();
            int price = rs.getInt("PRICE");
            stmt1.close();

            PreparedStatement stmt2 = conn.prepareStatement(
                    "UPDATE BOOK_STOCK SET STOCK = STOCK - 1 "+
                    "WHERE ISBN = ?");
            stmt2.setString(1, isbn);
            stmt2.executeUpdate();
            stmt2.close();

            PreparedStatement stmt3 = conn.prepareStatement(
                    "UPDATE ACCOUNT SET BALANCE = BALANCE - ? "+
                    "WHERE USERNAME = ?");
            stmt3.setInt(1, price);
            stmt3.setString(2, username);
            stmt3.executeUpdate();
            stmt3.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
} finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {}
            }
        }
    }
}

For the purchase() operation, you have to execute three SQL statements in total. The first is to query the book price. The second and third update the book stock and account balance accordingly.

Then, you can declare a bookshop instance in the Spring IoC container to provide purchasing services. For simplicity's sake, you can use DriverManagerDataSource, which opens a new connection to the database for every request.

Note

To access a database running on the Derby server, you have to the Derby client library to your CLASSPATH. If you're using Maven, add the following dependency to your project.

<dependency>
  <groupId>org.apache.derby</groupId>
  <artifactId>derbyclient</artifactId>
  <version>10.4.2.0</version>
 </dependency>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName"
            value="org.apache.derby.jdbc.ClientDriver"/>
        <property name="url"
            value="jdbc:derby://localhost:1527/bookshop;create=true"/>
        <property name="username"value="app"/>
        <property name="password"value="app"/>
    </bean>
<bean id="bookShop"
class="com.apress.springrecipes.bookshop.spring.JdbcBookShop">
        <property name="dataSource"ref="dataSource"/>
    </bean>
</beans>

To demonstrate the problems that can arise without transaction management, suppose you have the data shown in Tables 16-2, 16-3, and 16-4 entered in your bookshop database.

Table 16.2. Sample Data in the BOOK Table for Testing Transactions

ISBN

BOOK_NAME

PRICE

0001

The First Book

30

Table 16.3. Sample Data in the BOOK_STOCK Table for Testing Transactions

ISBN

STOCK

0001 10

 

Table 16.4. Sample Data in the ACCOUNT Table for Testing Transactions

USERNAME

BALANCE

user1 20

 

Then, write the following Main class for purchasing the book with ISBN 0001 by the user user1. Because that user's account has only $20, is the funds are not sufficient to purchase the book.

package com.apress.springrecipes.bookshop.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {
        ApplicationContext context =
            new ClassPathXmlApplicationContext("beans.xml");

        BookShop bookShop = (BookShop) context.getBean("bookShop");
        bookShop.purchase("0001", "user1");
    }
}

When you run this application, you will encounter a SQLException, because the CHECK constraint of the ACCOUNT table has been violated. This is an expected result because you were trying to debit more than the account balance. However, if you check the stock for this book in the BOOK_STOCK table, you will find that it was accidentally deducted by this unsuccessful operation! The reason is that you executed the second SQL statement to deduct the stock before you got an exception in the third statement.

As you can see, the lack of transaction management causes your data to be left in an inconsistent state. To avoid this inconsistency, your three SQL statements for the purchase() operation should be executed within a single transaction. Once any of the actions in a transaction fail, the entire transaction should be rolled back to undo all changes made by the executed actions.

Managing Transactions with JDBC Commit and Rollback

When using JDBC to update a database, by default, each SQL statement will be committed immediately after its execution. This behavior is known as auto-commit. However, it does not allow you to manage transactions for your operations.

JDBC supports the primitive transaction management strategy of explicitly calling the commit() and rollback() methods on a connection. But before you can do that, you must turn off auto-commit, which is turned on by default.

package com.apress.springrecipes.bookshop.spring;
...
public class JdbcBookShop implements BookShop {
    ...
    public void purchase(String isbn, String username) {
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            conn.setAutoCommit(false);
            ...
            conn.commit();
        } catch (SQLException e) {
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException e1) {}
            }
            throw new RuntimeException(e);
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {}
            }
        }
    }
}

The auto-commit behavior of a database connection can be altered by calling the setAutoCommit() method. By default, auto-commit is turned on to commit each SQL statement immediately after its execution. To enable transaction management, you must turn off this default behavior and commit the connection only when all the SQL statements have been executed successfully. If any of the statements go wrong, you must roll back all changes made by this connection.

Now, if you run your application again, the book stock will not be deducted when the user's balance is insufficient to purchase the book.

Although you can manage transactions by explicitly committing and rolling back JDBC connections, the code required for this purpose is boilerplate code that you have to repeat for different methods. Moreover, this code is JDBC specific, so once you have chosen another data access technology, it needs to be changed also. Spring's transaction support offers a set of technology-independent facilities, including transaction managers (e.g., org.springframework.transaction.PlatformTransactionManager), a transaction template (e.g., org.springframework.transaction.support.TransactionTemplate), and transaction declaration support to simplify your transaction management tasks.

Choosing a Transaction Manager Implementation

Problem

Typically, if your application involves only a single data source, you can simply manage transactions by calling the commit() and rollback() methods on a database connection. However, if your transactions extend across multiple data sources or you prefer to make use of the transaction management capabilities provided by your Java EE application server, you may choose the Java Transaction API (JTA). Besides, you may have to call different proprietary transaction APIs for different object/relational mapping frameworks such as Hibernate and JPA.

As a result, you have to deal with different transaction APIs for different technologies. It would be hard for you to switch from one set of APIs to another.

Solution

Spring abstracts a general set of transaction facilities from different transaction management APIs. As an application developer, you can simply utilize Spring's transaction facilities without having to know much about the underlying transaction APIs. With these facilities, your transaction management code will be independent of any specific transaction technology.

Spring's core transaction management abstraction is based on the interface PlatformTransactionManager. It encapsulates a set of technology-independent methods for transaction management. Remember that a transaction manager is needed no matter which transaction management strategy (programmatic or declarative) you choose in Spring. The PlatformTransactionManager interface provides three methods for working with transactions:

  • TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException

  • void commit(TransactionStatus status) throws TransactionException;

  • void rollback(TransactionStatus status) throws TransactionException;

How It Works

PlatformTransactionManager is a general interface for all Spring transaction managers. Spring has several built-in implementations of this interface for use with different transaction management APIs:

  • If you have to deal with only a single data source in your application and access it with JDBC, DataSourceTransactionManager should meet your needs.

  • If you are using JTA for transaction management on a Java EE application server, you should use JtaTransactionManager to look up a transaction from the application server. Additionally, JtaTransactionManager is appropriate for distributed transactions (transactions that span multiple resources). Note that while it's common to use a JTA transaction manager to integrate the application servers' transaction manager, there's nothing stopping you from using a stand-alone JTA transaction manager such as Atomikos.

  • If you are using an object/relational mapping framework to access a database, you should choose a corresponding transaction manager for this framework, such as HibernateTransactionManager and JpaTransactionManager.

Figure 16-2 shows the common implementations of the PlatformTransactionManager interface in Spring.

Common implementations of the PlatformTransactionManager interface

Figure 16.2. Common implementations of the PlatformTransactionManager interface

A transaction manager is declared in the Spring IoC container as a normal bean. For example, the following bean configuration declares a DataSourceTransactionManager instance. It requires the dataSource property to be set so that it can manage transactions for connections made by this data source.

<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

Managing Transactions Programmatically with the Transaction Manager API

Problem

You need to precisely control when to commit and roll back transactions in your business methods, but you don't want to deal with the underlying transaction API directly.

Solution

Spring's transaction manager provides a technology-independent API that allows you to start a new transaction (or obtain the currently active transaction) by calling the getTransaction() method and manage it by calling the commit() and rollback() methods. Because PlatformTransactionManager is an abstract unit for transaction management, the methods you called for transaction management are guaranteed to be technology independent.

How It Works

To demonstrate how to use the transaction manager API, let's create a new class, TransactionalJdbcBookShop, which will make use of the Spring JDBC template. Because it has to deal with a transaction manager, you add a property of type PlatformTransactionManager and allow it to be injected via a setter method.

package com.apress.springrecipes.bookshop.spring;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
public class TransactionalJdbcBookShop extends JdbcDaoSupport implements
        BookShop {

    private PlatformTransactionManager transactionManager;

    public void setTransactionManager(
            PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void purchase(String isbn, String username) {
        TransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);

        try {
            int price = getJdbcTemplate().queryForInt(
                    "SELECT PRICE FROM BOOK WHERE ISBN = ?",
                    new Object[] { isbn });

            getJdbcTemplate().update(
                    "UPDATE BOOK_STOCK SET STOCK = STOCK - 1 "+
                    "WHERE ISBN = ?", new Object[] { isbn });

            getJdbcTemplate().update(
                    "UPDATE ACCOUNT SET BALANCE = BALANCE - ? "+
                    "WHERE USERNAME = ?",
                    new Object[] { price, username });

            transactionManager.commit(status);
        } catch (DataAccessException e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

Before you start a new transaction, you have to specify the transaction attributes in a transaction definition object of type TransactionDefinition. For this example, you can simply create an instance of DefaultTransactionDefinition to use the default transaction attributes.

Once you have a transaction definition, you can ask the transaction manager to start a new transaction with that definition by calling the getTransaction() method. Then, it will return a TransactionStatus object to keep track of the transaction status. If all the statements execute successfully, you ask the transaction manager to commit this transaction by passing in the transaction status. Because all exceptions thrown by the Spring JDBC template are subclasses of DataAccessException, you ask the transaction manager to roll back the transaction when this kind of exception is caught.

In this class, you have declared the transaction manager property of the general type PlatformTransactionManager. Now, you have to inject an appropriate transaction manager implementation. Because you are dealing with only a single data source and accessing it with JDBC, you should choose DataSourceTransactionManager. Here, you also wire a dataSource because the class is a subclass of Spring's JdbcDaoSupport, which requires it.

<beans ...>
    ...
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="bookShop"
        class="com.apress.springrecipes.bookshop.spring.TransactionalJdbcBookShop">
        <property name="dataSource" ref="dataSource"/>
        <property name="transactionManager" ref="transactionManager"/>
    </bean>
</beans>

Managing Transactions Programmatically with a Transaction Template

Problem

Suppose that you have a code block, but not the entire body, of a business method that has the following transaction requirements:

  • Start a new transaction at the beginning of the block.

  • Commit the transaction after the block completes successfully.

  • Roll back the transaction if an exception is thrown in the block.

If you call Spring's transaction manager API directly, the transaction management code can be generalized in a technology-independent manner. However, you may not want to repeat the boilerplate code for each similar code block.

Solution

As with the JDBC template, Spring also provides a TransactionTemplate to help you control the overall transaction management process and transaction exception handling. You just have to encapsulate your code block in a callback class that implements the TransactionCallback<T> interface and pass it to the TransactionTemplate's execute method for execution. In this way, you don't need to repeat the boilerplate transaction management code for this block. The template objects that Spring provides are lightweight and usually can be discarded or re-created with no performance impact. A JDBC template can be re-created on the fly with a DataSource reference, for example, and so too can a TransactionTemplate be re-created by providing a reference to a transaction manager. You can, of course, simply create one in your Spring application context, too.

How It Works

A TransactionTemplate is created on a transaction manager just as a JDBC template is created on a data source. A transaction template executes a transaction callback object that encapsulates a transactional code block. You can implement the callback interface either as a separate class or as an inner class. If it's implemented as an inner class, you have to make the method arguments final for it to access.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

public class TransactionalJdbcBookShop extends JdbcDaoSupport implements
        BookShop {

    private PlatformTransactionManager transactionManager;

    public void setTransactionManager(
            PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void purchase(final String isbn, final String username) {
        TransactionTemplate transactionTemplate =
            new TransactionTemplate(transactionManager);

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {

            protected void doInTransactionWithoutResult(
                    TransactionStatus status) {

                int price = getJdbcTemplate().queryForInt(
                        "SELECT PRICE FROM BOOK WHERE ISBN = ?",
                        new Object[] { isbn });

                getJdbcTemplate().update(
                        "UPDATE BOOK_STOCK SET STOCK = STOCK - 1 "+
                        "WHERE ISBN = ?", new Object[] { isbn });

                getJdbcTemplate().update(
                        "UPDATE ACCOUNT SET BALANCE = BALANCE - ? "+
                        "WHERE USERNAME = ?",
                        new Object[] { price, username });
            }
        });
    }
}

A TransactionTemplate can accept a transaction callback object that implements either the TransactionCallback<T> or an instance of the one implementor of that interface provided by the framework, the TransactionCallbackWithoutResult class. For the code block in the purchase() method for deducting the book stock and account balance, there's no result to be returned, so TransactionCallbackWithoutResult is fine. For any code blocks with return values, you should implement the TransactionCallback<T> interface instead. The return value of the callback object will finally be returned by the template's T execute() method. The main benefit is that the responsibility of starting, rolling back, or committing the transaction has been removed.

During the execution of the callback object, if it throws an unchecked exception (e.g., RuntimeException and DataAccessException fall into this category), or if you explicitly called setRollbackOnly() on the TransactionStatus argument in the doInTransactionWithoutResult method, the transaction will be rolled back. Otherwise, it will be committed after the callback object completes.

In the bean configuration file, the bookshop bean still requires a transaction manager to create a TransactionTemplate.

<beans ...>
    ...
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource"ref="dataSource"/>
    </bean>

    <bean id="bookShop"
        class="com.apress.springrecipes.bookshop.spring.
TransactionalJdbcBookShop">
        <property name="dataSource" ref="dataSource"/>
        <property name="transactionManager" ref="transactionManager"/>
    </bean>
</beans>

You can also have the IoC container inject a transaction template instead of creating it directly. Because a transaction template handles all transactions, there's no need for your class to refer to the transaction manager any more.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.support.TransactionTemplate;

public class TransactionalJdbcBookShop extends JdbcDaoSupport implements
        BookShop {

    private TransactionTemplate transactionTemplate;

    public void setTransactionTemplate(
            TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }
public void purchase(final String isbn, final String username) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                ...
            }
        });
    }
}

Then you define a transaction template in the bean configuration file and inject it, instead of the transaction manager, into your bookshop bean. Notice that the transaction template instance can be used for more than one transactional bean because it is a thread-safe object. Finally, don't forget to set the transaction manager property for your transaction template.

<beans ...>
    ...
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="transactionTemplate"
        class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

    <bean id="bookShop"
        class="com.apress.springrecipes.bookshop.spring.TransactionalJdbcBookShop">
        <property name="dataSource" ref="dataSource"/>
        <property name="transactionTemplate" ref="transactionTemplate"/>
    </bean>
</beans>

Managing Transactions Declaratively with Transaction Advices

Problem

Because transaction management is a kind of crosscutting concern, you should manage transactions declaratively with the AOP approach available from Spring 2.x onward. Managing transactions manually can be tedious and error prone. It is simpler to specify, declaratively, what behavior you are expecting and to not prescribe how that behavior is to be achieved.

Solution

Spring (since version 2.0) offers a transaction advice that can be easily configured via the <tx:advice> element defined in the tx schema. This advice can be enabled with the AOP configuration facilities defined in the aop saop schema.

How It Works

To enable declarative transaction management, you can declare a transaction advice via the <tx:advice> element defined in the tx schema, so you have to add this schema definition to the <beans> root element beforehand. Once you have declared this advice, you need to associate it with a pointcut. Because a transaction advice is declared outside the <aop:config> element, it cannot link with a pointcut directly. You have to declare an advisor in the <aop:config> element to associate an advice with a pointcut.

Note

Because Spring AOP uses the AspectJ pointcut expressions to define pointcuts, you have to include the AspectJ Weaver support on your CLASSPATH. If you're using Maven, add the following dependency to your project.

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.6.8</version>
 </dependency>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <tx:advice id="bookShopTxAdvice"
        transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="purchase"/>
        </tx:attributes>
    </tx:advice>
<aop:config>
        <aop:pointcut id="bookShopOperation" expression=
            "execution(* com.apress.springrecipes.bookshop.spring.
BookShop.*(..))"/>
        <aop:advisor advice-ref="bookShopTxAdvice"
            pointcut-ref="bookShopOperation"/>
    </aop:config>
    ...
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource"ref="dataSource"/>
    </bean>

    <bean id="bookShop"
        class="com.apress.springrecipes.bookshop.spring.JdbcBookShop">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

The preceding AspectJ pointcut expression matches all the methods declared in the BookShop interface. However, because Spring AOP is based on proxies, it can apply only to public methods. Thus only public methods can be made transactional with Spring AOP.

Each transaction advice requires an identifier and a reference to a transaction manager in the IoC container. If you don't specify a transaction manager explicitly, Spring will search the application context for a TransactionManager with a bean name of transactionManager. The methods that require transaction management are specified with multiple <tx:method> elements inside the <tx:attributes> element. The method name supports wildcards for you to match a group of methods. You can also define transaction attributes for each group of methods, but let's use the default attributes for simplicity's sake. The defaults are shown in Table 16-5.

Table 16.5. Attributes Used with tx:attributes

Attribute

Required

Default

Description

name

Yes

n/a

The name of the methods against which the advice will be applied. You can use wildcards (*).

propagation

No

REQUIRED

The propagation specification for the transaction.

isolation

No

DEFAULT

The isolation level specification for the transaction.

timeout

No

−1

How long (in seconds) the transaction will attempt to commit before it times out.

read-only

No

False

Tells the container whether the transaction is read-only or not. This is a Spring-specific setting. If you're used to standard Java EE transaction configuration, you won't have seen this setting before. Its meaning is different for different resources (e.g., databases have a different notion of "read-only" than a JMS queue does).

rollback-for

No

N/A

Comma-delimited list of fully qualified Exception types that, when thrown from the method, the transaction should rollback for.

no-rollback-for

No

N/A

A comma-delimited list of Exception types that, when thrown from the method, the transaction should ignore and not roll back for.

Now, you can retrieve the bookShop bean from the Spring IoC container to use. Because this bean's methods are matched by the pointcut, Spring will return a proxy that has transaction management enabled for this bean.

package com.apress.springrecipes.bookshop.spring;
...
public class Main {

    public static void main(String[] args) {
        ...
        BookShop bookShop = (BookShop) context.getBean("bookShop");
        bookShop.purchase("0001", "user1");
    }
}

Managing Transactions Declaratively with the @Transactional Annotation

Problem

Declaring transactions in the bean configuration file requires knowledge of AOP concepts such as pointcuts, advices, and advisors. Developers who lack this knowledge might find it hard to enable declarative transaction management.

Solution

In addition to declaring transactions in the bean configuration file with pointcuts, advices, and advisors, Spring allows you to declare transactions simply by annotating your transactional methods with @Transactional and enabling the <tx:annotation-driven> element. However, Java 1.5 or higher is required to use this approach. Note that although you could apply the annotation to an interface method, it's not a recommended practice.

How It Works

To define a method as transactional, you can simply annotate it with @Transactional. Note that you should only annotate public methods due to the proxy-based limitations of Spring AOP.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.annotation.Transactional;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class JdbcBookShop extends JdbcDaoSupport implements BookShop {

    @Transactional
    public void purchase(String isbn, String username) {

      int price = getJdbcTemplate().queryForInt(
          "SELECT PRICE FROM BOOK WHERE ISBN = ?",
          new Object[] { isbn });

      getJdbcTemplate().update(
          "UPDATE BOOK_STOCK SET STOCK = STOCK - 1 "+
          "WHERE ISBN = ?", new Object[] { isbn });

      getJdbcTemplate().update(
          "UPDATE ACCOUNT SET BALANCE = BALANCE - ? "+
          "WHERE USERNAME = ?",
          new Object[] { price, username });
    }
}

Note that, as we are extending JdbcDaoSupport, we no longer need the mutators for the DataSource; remove it from your DAO class.

You may apply the @Transactional annotation at the method level or the class level. When applying this annotation to a class, all of the public methods within this class will be defined as transactional. Although you can apply @Transactional to interfaces or method declarations in an interface, it's not recommended because it may not work properly with class-based proxies (i.e., CGLIB proxies).

In the bean configuration file, you only have to enable the <tx:annotation-driven> element and specify a transaction manager for it. That's all you need to make it work. Spring will advise methods with @Transactional, or methods in a class with @Transactional, from beans declared in the IoC container. As a result, Spring can manage transactions for these methods.

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
    <tx:annotation-driven transaction-manager="transactionManager"/>
    ...
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="bookShop"
        class="com.apress.springrecipes.bookshop.spring.JdbcBookShop">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

In fact, you can omit the transaction-manager attribute in the <tx:annotation-driven> element if your transaction manager has the name transactionManager. This element will automatically detect a transaction manager with this name. You have to specify a transaction manager only when it has a different name.

<beans ...>
    <tx:annotation-driven />
    ...
</beans>

Setting the Propagation Transaction Attribute

Problem

When a transactional method is called by another method, it is necessary to specify how the transaction should be propagated. For example, the method may continue to run within the existing transaction, or it may start a new transaction and run within its own transaction.

Solution

A transaction's propagation behavior can be specified by the propagation transaction attribute. Spring defines seven propagation behaviors, as shown in Table 16-6. These behaviors are defined in the org.springframework.transaction.TransactionDefinition interface. Note that not all types of transaction managers support all of these propagation behaviors. Their behavior is contingent on the underlying resource. Databases, for example, may support varying isolation levels, which constrains what propagation behaviors the transaction manager can support.

Table 16.6. Propagation Behaviors Supported by Spring

Propagation

Description

REQUIRED

If there's an existing transaction in progress, the current method should run within this transaction. Otherwise, it should start a new transaction and run within its own transaction.

REQUIRES_NEW

The current method must start a new transaction and run within its own transaction. If there's an existing transaction in progress, it should be suspended.

SUPPORTS

If there's an existing transaction in progress, the current method can run within this transaction. Otherwise, it is not necessary to run within a transaction.

NOT_SUPPORTED

The current method should not run within a transaction. If there's an existing transaction in progress, it should be suspended.

MANDATORY

The current method must run within a transaction. If there's no existing transaction in progress, an exception will be thrown.

NEVER

The current method should not run within a transaction. If there's an existing transaction in progress, an exception will be thrown.

NESTED

If there's an existing transaction in progress, the current method should run within the nested transaction (supported by the JDBC 3.0 save point feature) of this transaction. Otherwise, it should start a new transaction and run within its own transaction. This feature is unique to Spring (whereas the previous propagation behaviors have analogs in Java EE transaction propagation). The behavior is useful for situations such as batch processing, in which you've got a long running process (imagine processing 1 million records) and you want to chunk the commits on the batch. So you commit every 10,000 records. If something goes wrong, you roll back the nested transaction and you've lost only 10,000 records' worth of work (as opposed to the entire 1 million).

How It Works

Transaction propagation happens when a transactional method is called by another method. For example, suppose that a customer would like to check out all books to purchase at the bookshop cashier. To support this operation, you define the Cashier interface as follows:

package com.apress.springrecipes.bookshop.spring;
...
public interface Cashier {

    public void checkout(List<String> isbns, String username);
}

You can implement this interface by delegating the purchases to a bookshop bean by calling its purchase() method multiple times. Note that the checkout() method is made transactional by applying the @Transactional annotation.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.annotation.Transactional;

public class BookShopCashier implements Cashier {

    private BookShop bookShop;

    public void setBookShop(BookShop bookShop) {
        this.bookShop = bookShop;
    }

    @Transactional
    public void checkout(List<String> isbns, String username) {
        for (String isbn : isbns) {
            bookShop.purchase(isbn, username);
        }
    }
}

Then define a cashier bean in your bean configuration file and refer to the bookshop bean for purchasing books.

<bean id="cashier"
    class="com.apress.springrecipes.bookshop.spring.BookShopCashier">
    <property name="bookShop" ref="bookShop"/>
</bean>

To illustrate the propagation behavior of a transaction, enter the data shown in Tables 16-7, 16-8, and 16-9 in your bookshop database.

Table 16.7. Sample Data in the BOOK Table for Testing Propagation Behaviors

ISBN

BOOK_NAME

PRICE

0001

The First Book

30

0002

The Second Book

50

Table 16.8. Sample Data in the BOOK_STOCK Table for Testing Propagation Behaviors

ISBN

STOCK

0001 10

 

0002 10

 

Table 16.9. Sample Data in the ACCOUNT Table for Testing Propagation Behaviors

USERNAME

BALANCE

user1 40

 

The REQUIRED Propagation Behavior

When the user user1 checks out the two books from the cashier, the balance is sufficient to purchase the first book but not the second.

package com.apress.springrecipes.bookshop.spring;
...
public class Main {

    public static void main(String[] args) {
        ...
        Cashier cashier = (Cashier) context.getBean("cashier");
        List<String> isbnList =
                Arrays.asList(new String[] { "0001", "0002"});
        cashier.checkout(isbnList, "user1");
    }
}

When the bookshop's purchase() method is called by another transactional method, such as checkout(), it will run within the existing transaction by default. This default propagation behavior is called REQUIRED. That means there will be only one transaction whose boundary is the beginning and ending of the checkout() method. This transaction will be committed only at the end of the checkout() method. As a result, the user can purchase none of the books. Figure 16-3 illustrates the REQUIRED propagation behavior.

The REQUIRED transaction propagation behavior

Figure 16.3. The REQUIRED transaction propagation behavior

However, if the purchase() method is called by a non-transactional method and there's no existing transaction in progress, it will start a new transaction and run within its own transaction.

The propagation transaction attribute can be defined in the @Transactional annotation. For example, you can set the REQUIRED behavior for this attribute as follows. In fact, this is unnecessary, because it's the default behavior.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class JdbcBookShop extends JdbcDaoSupport implements BookShop {
    @Transactional(propagation = Propagation.REQUIRED)
    public void purchase(String isbn, String username) {
        ...
    }
}

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class BookShopCashier implements Cashier {
    ...
    @Transactional(propagation = Propagation.REQUIRED)
    public void checkout(List<String> isbns, String username) {
        ...
    }
}

The REQUIRES_NEW Propagation Behavior

Another common propagation behavior is REQUIRES_NEW. It indicates that the method must start a new transaction and run within its new transaction. If there's an existing transaction in progress, it should be suspended first (as, for example, with the checkout method on BookShopCashier, with a propagation of REQUIRED).

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class JdbcBookShop extends JdbcDaoSupport implements BookShop {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void purchase(String isbn, String username) {
        ...
    }
}

In this case, there will be three transactions started in total. The first transaction is started by the checkout() method, but when the first purchase() method is called, the first transaction will be suspended and a new transaction will be started. At the end of the first purchase() method, the new transaction completes and commits. When the second purchase() method is called, another new transaction will be started. However, this transaction will fail and roll back. As a result, the first book will be purchased successfully, while the second will not. Figure 16-4 illustrates the REQUIRES_NEW propagation behavior.

The REQUIRES_NEW transaction propagation behavior

Figure 16.4. The REQUIRES_NEW transaction propagation behavior

Setting the Propagation Attribute in Transaction Advices, Proxies, and APIs

In a Spring transaction advice, the propagation transaction attribute can be specified in the <tx:method> element as follows:

<tx:advice ...>
    <tx:attributes>
        <tx:method name="..."
            propagation="REQUIRES_NEW"/>
    </tx:attributes>
</tx:advice>

In classic Spring AOP, the propagation transaction attribute can be specified in the transaction attributes of TransactionInterceptor and TransactionProxyFactoryBean as follows:

<property name="transactionAttributes">
    <props>
        <prop key="...">PROPAGATION_REQUIRES_NEW</prop>
    </props>
</property>

In Spring's transaction management API, the propagation transaction attribute can be specified in a DefaultTransactionDefinition object and then passed to a transaction manager's getTransaction() method or a transaction template's constructor.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

Setting the Isolation Transaction Attribute

Problem

When multiple transactions of the same application or different applications are operating concurrently on the same dataset, many unexpected problems may arise. You must specify how you expect your transactions to be isolated from one another.

Solution

The problems caused by concurrent transactions can be categorized into four types:

  • Dirty read: For two transactions T1 and T2, T1 reads a field that has been updated by T2 but not yet committed. Later, if T2 rolls back, the field read by T1 will be temporary and invalid.

  • Nonrepeatable read: For two transactions T1 and T2, T1 reads a field and then T2 updates the field. Later, if T1 reads the same field again, the value will be different.

  • Phantom read: For two transactions T1 and T2, T1 reads some rows from a table and then T2 inserts new rows into the table. Later, if T1 reads the same table again, there will be additional rows.

  • Lost updates: For two transactions T1 and T2, they both select a row for update, and based on the state of that row, make an update to it. Thus, one overwrites the other when the second transaction to commit should have waited until the first one committed before performing its selection.

In theory, transactions should be completely isolated from each other (i.e., serializable) to avoid all the mentioned problems. However, this isolation level will have great impact on performance, because transactions have to run in serial order. In practice, transactions can run in lower isolation levels in order to improve performance.

A transaction's isolation level can be specified by the isolation transaction attribute. Spring supports five isolation levels, as shown in Table 16-10. These levels are defined in the org.springframework.transaction.TransactionDefinition interface.

Table 16.10. Isolation Levels Supported by Spring

Isolation

Description

DEFAULT

Uses the default isolation level of the underlying database. For most databases, the default isolation level is READ_COMMITTED.

READ_UNCOMMITTED

Allows a transaction to read uncommitted changes by other transactions. The dirty read, nonrepeatable read, and phantom read problems may occur.

READ_COMMITTED

Allows a transaction to read only those changes that have been committed by other transactions. The dirty read problem can be avoided, but the nonrepeatable read and phantom read problems may still occur.

REPEATABLE_READ

Ensures that a transaction can read identical values from a field multiple times. For the duration of this transaction, updates made by other transactions to this field are prohibited. The dirty read and nonrepeatable read problems can be avoided, but the phantom read problem may still occur.

SERIALIZABLE

Ensures that a transaction can read identical rows from a table multiple times. For the duration of this transaction, inserts, updates, and deletes made by other transactions to this table are prohibited. All the concurrency problems can be avoided, but the performance will be low.

Note that transaction isolation is supported by the underlying database engine but not an application or a framework. However, not all database engines support all these isolation levels. You can change the isolation level of a JDBC connection by calling the setTransactionIsolation() method on the java.sql.Connection ijava.sql.Connection interface.

How It Works

To illustrate the problems caused by concurrent transactions, let's add two new operations to your bookshop for increasing and checking the book stock.

package com.apress.springrecipes.bookshop.spring;

public interface BookShop {
    ...
    public void increaseStock(String isbn, int stock);
    public int checkStock(String isbn);
}

Then, you implement these operations as follows. Note that these two operations should also be declared as transactional.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.annotation.Transactional;

public class JdbcBookShop extends JdbcDaoSupport implements BookShop {
    ...
    @Transactional
    public void increaseStock(String isbn, int stock) {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "- Prepare to increase book stock");

        getJdbcTemplate().update(
                "UPDATE BOOK_STOCK SET STOCK = STOCK + ? "+
                "WHERE ISBN = ?",
                new Object[] { stock, isbn });

        System.out.println(threadName + "- Book stock increased by "+ stock);
        sleep(threadName);

        System.out.println(threadName + "- Book stock rolled back");
        throw new RuntimeException("Increased by mistake");
    }

    @Transactional
    public int checkStock(String isbn) {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "- Prepare to check book stock");

        int stock = getJdbcTemplate().queryForInt(
                "SELECT STOCK FROM BOOK_STOCK WHERE ISBN = ?",
                new Object[] { isbn });

        System.out.println(threadName + "- Book stock is "+ stock);
        sleep(threadName);

        return stock;
    }
private void sleep(String threadName) {
        System.out.println(threadName + "- Sleeping");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {}
        System.out.println(threadName + "- Wake up");
    }
}

To simulate concurrency, your operations need to be executed by multiple threads. You can track the current status of the operations through the println statements. For each operation, you print a couple of messages to the console around the SQL statement's execution. The messages should include the thread name for you to know which thread is currently executing the operation.

After each operation executes the SQL statement, you ask the thread to sleep for 10 seconds. As you know, the transaction will be committed or rolled back immediately once the operation completes. Inserting a sleep statement can help to postpone the commit or rollback. For the increase() operation, you eventually throw a RuntimeException to cause the transaction to roll back. Let's look at a simple client that runs these examples.

Before you start with the isolation level examples, enter the data from Tables 16-11 and 16-12 into your bookshop database. (Note that the ACCOUNT table isn't needed in this example.)

Table 16.11. Sample Data in the BOOK Table for Testing Isolation Levels

ISBN

BOOK_NAME

PRICE

0001

The First Book

30

Table 16.12. Sample Data in the BOOK_STOCK Table for Testing Isolation Levels

ISBN

STOCK

0001 10

 

The READ_UNCOMMITTED and READ_COMMITTED Isolation Levels

READ_UNCOMMITTED is the lowest isolation level that allows a transaction to read uncommitted changes made by other transactions. You can set this isolation level in the @Transaction annotation of your checkStock() method.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
public class JdbcBookShop extends JdbcDaoSupport implements BookShop {
    ...
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public int checkStock(String isbn) {
        ...
    }
}

You can create some threads to experiment on this transaction isolation level. In the following Main class, there are two threads you are going to create. Thread 1 increases the book stock, while thread 2 checks the book stock. Thread 1 starts 5 seconds before thread 2.

package com.apress.springrecipes.bookshop.spring;
...
public class Main {

    public static void main(String[] args) {
        ...
        final BookShop bookShop = (BookShop) context.getBean("bookShop");

        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                try {
                    bookShop.increaseStock("0001", 5);
                } catch (RuntimeException e) {}
            }
        }, "Thread 1");

        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                bookShop.checkStock("0001");
            }
        }, "Thread 2");

        thread1.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {}
        thread2.start();
    }
}

If you run the application, you will get the following result:

Thread 1—Prepare to increase book stock

Thread 1—Book stock increased by 5

Thread 1—Sleeping

Thread 2—Prepare to check book stock

Thread 2—Book stock is 15

Thread 2—Sleeping

Thread 1—Wake up

Thread 1—Book stock rolled back

Thread 2—Wake up

First, thread 1 increased the book stock and then went to sleep. At that time, thread 1's transaction had not yet been rolled back. While thread 1 was sleeping, thread 2 started and attempted to read the book stock. With the READ_UNCOMMITTED isolation level, thread 2 would be able to read the stock value that had been updated by an uncommitted transaction.

However, when thread 1 wakes up, its transaction will be rolled back due to a RuntimeException, so the value read by thread 2 is temporary and invalid. This problem is known as dirty read, because a transaction may read values that are "dirty."

To avoid the dirty read problem, you should raise the isolation level of checkStock() to READ_COMMITTED.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

public class JdbcBookShop extends JdbcDaoSupport implements BookShop {
    ...
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public int checkStock(String isbn) {
        ...
    }
}

If you run the application again, thread 2 won't be able to read the book stock until thread 1 has rolled back the transaction. In this way, the dirty read problem can be avoided by preventing a transaction from reading a field that has been updated by another uncommitted transaction.

Thread 1—Prepare to increase book stock

Thread 1—Book stock increased by 5

Thread 1—Sleeping

Thread 2—Prepare to check book stock

Thread 1—Wake up

Thread 1—Book stock rolled back

Thread 2—Book stock is 10

Thread 2—Sleeping

Thread 2—Wake up

In order for the underlying database to support the READ_COMMITTED isolation level, it may acquire an update lock on a row that was updated but not yet committed. Then, other transactions must wait to read that row until the update lock is released, which happens when the locking transaction commits or rolls back.

The REPEATABLE_READ Isolation Level

Now, let's restructure the threads to demonstrate another concurrency problem. Swap the tasks of the two threads so that thread 1 checks the book stock before thread 2 increases the book stock.

package com.apress.springrecipes.bookshop.spring;
...
public class Main {

    public static void main(String[] args) {
        ...
        final BookShop bookShop = (BookShop) context.getBean("bookShop");

        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                bookShop.checkStock("0001");
            }
        }, "Thread 1");
Thread thread2 = new Thread(new Runnable() {
            public void run() {
                try {
                    bookShop.increaseStock("0001", 5);
                } catch (RuntimeException e) {}
            }
        }, "Thread 2");

        thread1.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {}
        thread2.start();
    }
}

If you run the application, you will get the following result:

Thread 1—Prepare to check book stock

Thread 1—Book stock is 10

Thread 1—Sleeping

Thread 2—Prepare to increase book stock

Thread 2—Book stock increased by 5

Thread 2—Sleeping

Thread 1—Wake up

Thread 2—Wake up

Thread 2—Book stock rolled back

First, thread 1 read the book stock and then went to sleep. At that time, thread 1's transaction had not yet been committed. While thread 1 was sleeping, thread 2 started and attempted to increase the book stock. With the READ_COMMITTED isolation level, thread 2 would be able to update the stock value that was read by an uncommitted transaction.

However, if thread 1 reads the book stock again, the value will be different from its first read. This problem is known as nonrepeatable read because a transaction may read different values for the same field.

To avoid the nonrepeatable read problem, you should raise the isolation level of checkStock() to REPEATABLE_READ.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

public class JdbcBookShop extends JdbcDaoSupport implements BookShop {
    ...
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public int checkStock(String isbn) {
        ...
    }
}

If you run the application again, thread 2 won't be able to update the book stock until thread 1 has committed the transaction. In this way, the nonrepeatable read problem can be avoided by preventing a transaction from updating a value that has been read by another uncommitted transaction.

Thread 1—Prepare to check book stock

Thread 1—Book stock is 10

Thread 1—Sleeping

Thread 2—Prepare to increase book stock

Thread 1—Wake up

Thread 2—Book stock increased by 5

Thread 2—Sleeping

Thread 2—Wake up

Thread 2—Book stock rolled back

In order for the underlying database to support the REPEATABLE_READ isolation level, it may acquire a read lock on a row that was read but not yet committed. Then, other transactions must wait to update the row until the read lock is released, which happens when the locking transaction commits or rolls back.

The SERIALIZABLE Isolation Level

After a transaction has read several rows from a table, another transaction inserts new rows into the same table. If the first transaction reads the same table again, it will find additional rows that are different from the first read. This problem is known as phantom read. Actually, phantom read is very similar to nonrepeatable read but involves multiple rows.

To avoid the phantom read problem, you should raise the isolation level to the highest: SERIALIZABLE. Notice that this isolation level is the slowest because it may acquire a read lock on the full table. In practice, you should always choose the lowest isolation level that can satisfy your requirements.

Setting the Isolation Level Attribute in Transaction Advices, Proxies, and APIs

In a Spring transaction advice, the isolation level can be specified in the <tx:method> element as follows:

<tx:advice ...>
    <tx:attributes>
        <tx:method name="*"
            isolation="REPEATABLE_READ"/>
    </tx:attributes>
</tx:advice>

In classic Spring AOP, the isolation level can be specified in the transaction attributes of TransactionInterceptor and TransactionProxyFactoryBean as follows:

<property name="transactionAttributes">
    <props>
        <prop key="...">
            PROPAGATION_REQUIRED, ISOLATION_REPEATABLE_READ
        </prop>
    </props>
</property>

In Spring's transaction management API, the isolation level can be specified in a DefaultTransactionDefinition object and then passed to a transaction manager's getTransaction() method or a transaction template's constructor.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);

Setting the Rollback Transaction Attribute

Problem

By default, only unchecked exceptions (i.e., of type RuntimeException and Error) will cause a transaction to roll back, while checked exceptions will not. Sometimes, you may wish to break this rule and set your own exceptions for rolling back.

Solution

The exceptions that cause a transaction to roll back or not can be specified by the rollback transaction attribute. Any exceptions not explicitly specified in this attribute will be handled by the default rollback rule (i.e., rolling back for unchecked exceptions and not rolling back for checked exceptions).

How It Works

A transaction's rollback rule can be defined in the @Transactional annotation via the rollbackFor and noRollbackFor attributes. These two attributes are declared as Class[], so you can specify more than one exception for each attribute.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;

public class JdbcBookShop extends JdbcDaoSupport implements BookShop {
    ...
    @Transactional(
            propagation = Propagation.REQUIRES_NEW,
            rollbackFor = IOException.class,
            noRollbackFor = ArithmeticException.class)
    public void purchase(String isbn, String username) throws Exception{
   throw new ArithmeticException();
             //throw new IOException();
    }
}

In a Spring transaction advice, the rollback rule can be specified in the <tx:method> element. You can separate the exceptions with commas if there's more than one exception.

<tx:advice ...>
    <tx:attributes>
        <tx:method name="..."
            rollback-for="java.io.IOException"
            no-rollback-for="java.lang.ArithmeticException"/>
        ...
    </tx:attributes>
</tx:advice>

In classic Spring AOP, the rollback rule can be specified in the transaction attributes of TransactionInterceptor and TransactionProxyFactoryBean. The minus sign indicates an exception to cause a transaction to roll back, while the plus sign indicates an exception to cause a transaction to commit.

<property name="transactionAttributes">
    <props>
        <prop key="...">
            PROPAGATION_REQUIRED, -java.io.IOException,
            +java.lang.ArithmeticException
        </prop>
    </props>
</property>

In Spring's transaction management API, the rollback rule can be specified in a RuleBasedTransactionAttribute object. Because it implements the TransactionDefinition interface, it can be passed to a transaction manager's getTransaction() method or a transaction template's constructor.

RuleBasedTransactionAttribute attr = new RuleBasedTransactionAttribute();
attr.getRollbackRules().add(
    new RollbackRuleAttribute(IOException.class));
attr.getRollbackRules().add(
    new NoRollbackRuleAttribute(SendFailedException.class));

Setting the Timeout and Read-Only Transaction Attributes

Problem

Because a transaction may acquire locks on rows and tables, a long transaction will tie up resources and have an impact on overall performance. Besides, if a transaction only reads but does not update data, the database engine could optimize this transaction. You can specify these attributes to increase the performance of your application.

Solution

The timeout transaction attribute (an integer that describes seconds) indicates how long your transaction can survive before it is forced to roll back. This can prevent a long transaction from tying up resources. The read-only attribute indicates that this transaction will only read but not update data. The read-only flag is just a hint to enable a resource to optimize the transaction, and a resource might not necessarily cause a failure if a write is attempted.

How It Works

The timeout and read-only transaction attributes can be defined in the @Transactional annotation. Note that timeout is measured in seconds.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

public class JdbcBookShop extends JdbcDaoSupport implements BookShop {
    ...
    @Transactional(
            isolation = Isolation.REPEATABLE_READ,
            timeout = 30,
            readOnly = true)
public int checkStock(String isbn) {
        ...
    }
}

In a Spring 2.0 transactional advice, the timeout and read-only transaction attributes can be specified in the <tx:method> element.

<tx:advice ...>
    <tx:attributes>
        <tx:method name="checkStock"
            timeout="30"
            read-only="true"/>
    </tx:attributes>
</tx:advice>

In classic Spring AOP, the timeout and read-only transaction attributes can be specified in the transaction attributes of TransactionInterceptor and TransactionProxyFactoryBean.

<property name="transactionAttributes">
    <props>
        <prop key="...">
            PROPAGATION_REQUIRED, timeout_30, readOnly
        </prop>
    </props>
</property>

In Spring's transaction management API, the timeout and read-only transaction attributes can be specified in a DefaultTransactionDefinition object and then passed to a transaction manager's getTransaction() method or a transaction template's constructor.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setTimeout(30);
def.setReadOnly(true);

Managing Transactions with Load-Time Weaving

Problem

By default, Spring's declarative transaction management is enabled via its AOP framework. However, as Spring AOP can only advise public methods of beans declared in the IoC container, you are restricted to managing transactions within this scope using Spring AOP. Sometimes, you may wish to manage transactions for nonpublic methods, or methods of objects created outside the Spring IoC container (e.g., domain objects).

Solution

Spring 2.5 also provides an AspectJ aspect named AnnotationTransactionAspect that can manage transactions for any methods of any objects, even if the methods are non-public or the objects are created outside the Spring IoC container. This aspect will manage transactions for any methods with the @Transactional annotation. You can choose either AspectJ's compile-time weaving or load-time weaving to enable this aspect.

How It Works

First of all, let's create a domain class Book, whose instances (i.e., domain objects) may be created outside the Spring IoC container.

package com.apress.springrecipes.bookshop.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.jdbc.core.JdbcTemplate;

@Configurable
public class Book {

    private String isbn;
    private String name;
    private int price;

    // Constructors, Getters and Setters
    ...

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void purchase(String username) {
        jdbcTemplate.update(
                "UPDATE BOOK_STOCK SET STOCK = STOCK - 1 "+
                "WHERE ISBN = ?",
                new Object[] { isbn });

        jdbcTemplate.update(
                "UPDATE ACCOUNT SET BALANCE = BALANCE - ? "+
                "WHERE USERNAME = ?",
                new Object[] { price, username });
    }
}

This domain class has a purchase() method that will deduct the current book instance's stock and the user account's balance from the database. To utilize Spring's powerful JDBC support features, you can inject the JDBC template via setter injection.

You can use Spring's load-time weaving support to inject a JDBC template into book domain objects. You have to annotate this class with @Configurable to declare that this type of object is configurable in the Spring IoC container. Moreover, you can annotate the JDBC template's setter method with @Autowired to have it auto-wired.

Spring includes an AspectJ aspect, AnnotationBeanConfigurerAspect, in its aspect library for configuring object dependencies even if these objects are created outside the IoC container. To enable this aspect, you just define the <context:spring-configured> element in your bean configuration file. To weave this aspect into your domain classes at load time, you also have to define <context:load-time-weaver>. Finally, to auto-wire the JDBC template into book domain objects via @Autowired, you need <context:annotation-config> also.

Note

To use the Spring aspect library for AspectJ in Spring 2.0 and 2.5, you have to include spring-aspects module on your CLASSPATH. In Spring 3.0, the library has been renamed spring-instrument. If you're using Maven, add the following dependency to your project.

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-instrument</artifactId>
  <version>${spring.version}</version>
 </dependency>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:load-time-weaver />

    <context:annotation-config />

    <context:spring-configured />

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName"
            value="org.apache.derby.jdbc.ClientDriver"/>
<property name="url"
            value="jdbc:derby://localhost:1527/bookshop;create=true"/>
        <property name="username" value="app"/>
        <property name="password" value="app"/>
    </bean>

    <bean id="jdbcTemplate"
        class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

In this bean configuration file, you can define a JDBC template on a data source, and then, it will be auto-wired into book domain objects for them to access the database.

Now, you can create the following Main class to test this domain class. Of course, there's no transaction support at this moment.

package com.apress.springrecipes.bookshop.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {
        ApplicationContext context =
            new ClassPathXmlApplicationContext("beans.xml");

        Book book = new Book("0001", "My First Book", 30);
        book.purchase("user1");
    }
}

For a simple Java application, you can weave this aspect into your classes at load time with the Spring agent specified as a VM argument.

java -javaagent: spring-instrument.jar
com.apress.springrecipes.bookshop.spring.Main

To enable transaction management for a domain object's method, you can simply annotate it with @Transactional, just as you did for methods of Spring beans.

package com.apress.springrecipes.bookshop.spring;
...
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.transaction.annotation.Transactional;
@Configurable
public class Book {
    ...
    @Transactional
    public void purchase(String username) {
        ...
    }
}

Finally, to enable Spring's AnnotationTransactionAspect for transaction management, you just define the <tx:annotation-driven> element and set its mode to aspectj. The <tx:annotation-driven> element takes two values for the mode attribute: aspectj and proxy. aspect stipulates that the container should use load-time or compile-time weaving to enable the transaction advice. This requires the spring-instrument jar to be on the classpath, as well as the appropriate configuration at load time or compile time. Alternatively, proxy stipulates that the container should use the Spring AOP mechanisms. It's important to note that the aspect mode doesn't support configuration of the @Transactional annotation on interfaces. Then the transaction aspect will automatically get enabled. You also have to provide a transaction manager for this aspect. By default, it will look for a transaction manager whose name is transactionManager.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
    ...
    <tx:annotation-driven mode="aspectj"/>

    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

Summary

This chapter discussed transactions and why you should use them. You explored the approach taken for transaction management historically in Java EE and then learned how the approach the Spring framework offers differs. You explored explicit use of transactions in your code as well as implicit use with annotation-driven aspects. You set up a database and used transactions to enforce valid state in the database.

In the next chapter, you will explore Spring's remoting support. Spring provides a layer to isolate your POJOs from the protocol and platform over which they are exposed to remote clients. You will explore the approach in general as well as see it applied to a few key technologies on the Java EE and Spring platforms.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset