Handling a transaction with Spring
Spring Framework provides supports for transaction management. The following are characteristics of the Spring transaction management framework:
- Offers abstraction for transaction management
- Defines a programming model that supports different transaction APIs, such as JDBC, JTA, and JPA
- Declarative transaction management is supported
- Provides a simpler programmatic transaction management API
- Easily integrates with Spring's data access abstractions
Two transaction management options are available for the J2EE developers. The following are the two options:
- The application server manages global transactions, using the Java Transaction API (JTA). It supports multiple transaction resources, such as database transactions, JMS transactions, and XA transactions.
- Resource-specific local transactions, such as a transaction associated with a JDBC connection.
Both transaction models have downsides. The global transaction needs an application server and JNDI to manage transactions; it uses JTA but the JTA API is cumbersome and has a complex exception model. The need for an application server, JNDI, and JTA limits the reusability of code.
The local transactions have the following disadvantages:
- Cannot handle multiple transactional resources
- Invasive to the programming model
Spring's transaction model solves the problems associated with the global and local transactions, and it offers a consistent programming model for developers that can be used in any environment.
Spring Framework supports both declarative and programmatic transaction management. Declarative transaction management is the recommended one, and it has been well accepted by the development community.
The programmatic transaction model provides an abstraction that can be run over any underlying transaction infrastructure. The concept of transaction strategy is the key to the transaction abstraction. The org.springframework.transaction.PlatformTransactionManager
interface defines the strategy.
The following is the PlatformTransactionManager
interface:
The following are the characteristics of PlatformTransactionManager
:
PlatformTransactionManager
is not a class; instead, it is an interface, and thus it can be easily mocked or stubbed to write tests.- It doesn't need a JNDI lookup strategy, as its implementations can be defined as Spring beans in Spring Framework's IoC container.
- Methods defined in
PlatformTransactionManager
throw TransactionException
. However, this is an unchecked exception, so programmers are not forced to handle the exception. But in reality, the exception is fatal in nature; when it is thrown, there is very little chance that the failure can be recovered. - The
getTransaction()
method takes a TransactionDefinition
parameter and returns a TransactionStatus
object. The TransactionStatus
object can be a new or an existing transaction.
The TransactionDefinition
interface defines the following:
- Isolation: This returns the degree of isolation of this transaction from other transactions. The following are the Spring propagations:
ISOLATION_DEFAULT
ISOLATION_READ_COMMITTED
ISOLATION_READ_UNCOMMITTED
ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE
- Propagation: This returns the transaction propagation behavior. The following are the allowable values:
PROPAGATION_MANDATORY
: This needs a current transaction and raises an error if no current transaction existsPROPAGATION_NESTED
: This executes the current transaction within a nested transactionPROPAGATION_NEVER
: This doesn't support a current transaction and raises an error if a current transaction existsPROPAGATION_NOT_SUPPORTED
: This executes code non-transactionallyPROPAGATION_REQUIRED
: This creates a new transaction if no transaction existsPROPAGATION_REQUIRES_NEW
: This suspends the current transaction and creates a new transactionPROPAGATION_SUPPORTS
: If the current transaction exists, then this supports it; otherwise, it executes the code non-transactionallyTIMEOUT_DEFAULT
: This uses the default timeout
- Timeout: This returns the maximum time in seconds that the current transaction should take; if the transaction takes more than that, then the transaction gets rolled back automatically.
- Read-only status: This returns whether the transaction is a read-only transaction. A read-only transaction does not modify any data.
The TransactionStatus
interface provides a simple way for transactional code to control the transaction execution and query the transaction status; it has the following signature:
The PlatformTransactionManager
implementations normally require knowledge of the environment in which they work, such as JDBC, JTA, Hibernate, and so on.
A local PlatformTransactionManager
implementation defines a JDBC data source and then uses the Spring DataSourceTransactionManager
class, which gives it a reference to DataSource
. The following Spring context defines a local transaction manager:
Here, ${jdbc.xxxx}
represents the values defined in the properties file. Usually, the convention is that the JDBC properties are defined in a properties file that is then loaded from applicationContext
, and then the JDBC properties are accessed using the key such as ${key}
. The following is the XML configuration of transaction manager:
When we use JTA in a J2EE container and use a container DataSource
obtained via the JNDI lookup, in conjunction with Spring's JtaTransactionManager
, then JtaTransactionManager
doesn't need to know about DataSource
, or any other specific resources, as it will use the container's global transaction management infrastructure.
The following is the JtaTransactionManager
definition in Spring context:
The benefit of Spring transaction manager is that in all cases, the application code will not need to change at all. We can change how transactions are managed merely by changing the configuration, even if that change means moving from local to global transactions or vice versa.
Declarative transaction management is preferred by most users; it is the option with the least impact on the application code. It is most consistent with the ideals of a non-invasive lightweight container. Spring's declarative transaction management is made possible with Spring AOP.
The similarities between the EJB CMT and Spring declarative transaction are as follows:
- It is possible to specify transaction behavior down to the individual method level
- It is possible to make a
setRollbackOnly()
call within a transaction context if necessary
Working with declarative Spring transaction
We'll create a simple Spring transaction management project and learn about the basics. The following are the steps to create the project:
- Create an empty class,
Foo
, under the com.packt.tx
package. The following is the class body: - Create an interface,
FooService
, to handle the CRUD operations on Foo
: - Create a default implementation of
FooService
, and from each method, throw UnsupportedOperationException
to impersonate a rollback transaction: - Create an application context file called
applicationContextTx.xml
directly under the src
folder and add the following entries:Define the fooService
bean:
Define a Derby data source:
Define a transaction manager with the data source:
Define an advice with transaction manager so that all get
methods will have a read-only
transaction:
Define the AOP configuration to apply the advice on pointcut
:
- Create a test class to get the
FooService
bean and call the getFoo
method on the FooService
bean. The following is the class: - When we run the program, Spring creates a transaction and then rolls back the transaction as it throws
UnsupportedOperationException
. Check the log to get the details. The following is the log:
Exploring transaction attributes
We declared a transaction advice and its attributes in the preceding example. This section examines the transaction attributes such as propagation, isolation, read-only, timeout, and rollback rules.
Transaction propagation has seven levels:
PROPAGATION_MANDATORY
: Method should run in a transaction and if nothing exists, an exception will be thrown.PROPAGATION_NESTED
: Method should run in a nested transaction.PROPAGATION_NEVER
: The current method should not run in a transaction. If this exists, an exception will be thrown.PROPAGATION_NOT_SUPPORTED
: Method should not run in a transaction. The existing transaction will be suspended till the method completes the execution.PROPAGATION_REQUIRED
: Method should run in a transaction. If this already exists, the method will run in that, and if not, a new transaction will be created.PROPAGATION_REQUIRES_NEW
: Method should run in a new transaction. If this already exists, it will be suspended till the method finishes.PROPAGATION_SUPPORTS
: Method need not run in a transaction. If this already exists, it supports one that is already in progress.
The following are the isolation levels:
ISOLATION_DEFAULT
: This is the default isolation specific to the data source.ISOLATION_READ_UNCOMMITTED
: This reads changes that are uncommitted. This leads to dirty reads, phantom reads, and non-repeatable reads.A dirty read happens when a transaction is allowed to read data from a row that has been modified by another running transaction and not yet committed.
Data getting changed in the current transaction by other transactions is known as a phantom read.
A non-repeatable read means data that is read twice inside the same transaction cannot be guaranteed to contain the same value.
ISOLATION_READ_COMMITTED
: This reads only committed data. Dirty reads are prevented but repeatable and non-repeatable reads are possible.ISOLATION_REPEATABLE_READ
: Multiple reads of the same field yield the same results unless modified by the same transaction. Dirty and non-repeatable reads are prevented but phantom reads are possible as other transactions can edit the fields.ISOLATION_SERIALIZABLE
: Dirty, phantom, and non-repeatable reads are prevented. However, this hampers the performance of the application.
The read-only attribute specifies that the transaction is only going to read data from a database. It can be applied to only those propagation settings that start a transaction, that is, PROPAGATION_REQUIRED
, PROPAGATION_REQUIRES_NEW
, and PROPAGATION_NESTED
.
The timeout specifies the maximum time allowed for a transaction to run. This is required for the transactions that run for very long and hold locks for a long time. When a transaction reaches the timeout period, it is rolled back. The timeout needs to be specified only on propagation settings that start a new transaction.
We can specify that transactions will roll back on certain exceptions and do not roll back on other exceptions by specifying the rollback rules.
Using the @Transactional annotation
The functionality offered by the @Transactional
annotation and the support classes is only available in Java 5 (Tiger) and above. The @Transactional
annotation can be placed before an interface definition, a method on an interface, a class definition, or a public method on a class. A method in the same class takes precedence over the transactional settings defined in the class-level annotation.
The following example demonstrates the method-level precedence:
However, the mere presence of the @Transactional
annotation is not enough to actually turn on the transactional behavior; the @Transactional
annotation is simply metadata that can be consumed by something that is aware of @Transactional
and that can use the metadata to configure the appropriate beans with the transactional behavior.
The default @Transactional
settings are as follows:
- The propagation setting is
PROPAGATION_REQUIRED
- The isolation level is
ISOLATION_DEFAULT
- The transaction is read/write
- The transaction timeout defaults to the default timeout of the underlying transaction system, or none if timeouts are not supported
- Any
RuntimeException
will trigger a rollback and any checked exception will not trigger a rollback
When the previous POJO is defined as a bean in a Spring IoC container, the bean instance can be made transactional by adding one line of XML configuration. We'll examine the @Transactional
annotation in the following example:
- Create a application context file called
applicationContextTxAnnotation.xml
and add the following lines (no need for aop
and advice
): - Annotate
FooServiceImpl
with the @Transactional
annotation: - Create a class called
TransactionTestAnnotation
, load applicationContextTxAnnotation
, and examine whether the same log appears. The following is the class:
Working with a programmatic Spring transaction
Spring provides two means of programmatic transaction management:
- Using
TransactionTemplate
- Using a
PlatformTransactionManager
implementation directly
The Spring team generally recommends the first approach (using TransactionTemplate
).
The second approach is similar to using the JTA UserTransaction
API (although exception handling is less cumbersome).
Using TransactionTemplate
The following are the characteristics of TransactionTemplate
:
- It adopts the same approach as other Spring templates such as
JdbcTemplate
and HibernateTemplate
- It uses a callback approach
- A
TransactionTemplate
instance is threadsafe
The following code snippet demonstrates TransactionTemplate
with a callback:
If there is no return value, use the convenient TransactionCallbackWithoutResult
class via an anonymous class, as follows:
Application classes wishing to use TransactionTemplate
must have access to PlatformTransactionManager
, which will typically be supplied to the class via a dependency injection. It is easy to unit test such classes with a mock or stub PlatformTransactionManager
. There is no JNDI lookup here; it is a simple interface. As usual, you can use Spring to greatly simplify your unit testing.
Using PlatformTransactionManager
A
PlatformTransactionManager
implementation can be directly used to manage a transaction:
- Simply pass the implementation of the
PlatformTransactionManager
to your bean via a bean reference. - Then, using the
TransactionDefinition
and TransactionStatus
objects, you can initiate transactions and perform a rollback or commit.
The following code snippet provides an example of such use:
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.