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:
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:
public interface PlatformTransactionManager { TransactionStatus getTransaction( TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
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
throwTransactionException
. 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 aTransactionDefinition
parameter and returns aTransactionStatus
object. TheTransactionStatus
object can be a new or an existing transaction.
The TransactionDefinition
interface defines the following:
public interface TransactionDefinition { int getIsolationLevel(); int getPropagationBehavior(); String getName(); int getTimeout(); boolean isReadOnly(); }
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:
public interface TransactionStatus { boolean isNewTransaction(); void setRollbackOnly(); boolean isRollbackOnly(); }
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:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean>
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:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
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:
<jee:jndi-lookup id="dataSource" jndi-name="myDataSource "/> <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
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
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 thecom.packt.tx
package. The following is the class body:package com.packt.tx; public class Foo { }
Create an interface,
FooService
, to handle the CRUD operations onFoo
:package com.packt.tx; public interface FooService { Foo getFoo(String fooName); void insertFoo(Foo foo); void updateFoo(Foo foo); }
Create a default implementation of
FooService
, and from each method, throwUnsupportedOperationException
to impersonate a rollback transaction:public class FooServiceImpl implements FooService { @Override public Foo getFoo(String fooName) { throw new UnsupportedOperationException(); } @Override public void insertFoo(Foo foo) { throw new UnsupportedOperationException(); } @Override public void updateFoo(Foo foo) { throw new UnsupportedOperationException(); } }
Create an application context file called
applicationContextTx.xml
directly under thesrc
folder and add the following entries:Define the
fooService
bean:<bean id="fooService" class="com.packt.tx.FooServiceImpl" />
Define a Derby data source:
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver" /> <property name="url" value="jdbc:derby:derbyDB;create=true" /> <property name="username" value="dbo" /> <property name="password" value="" /> </bean>
Define a transaction manager with the data source:
<bean id="txManager" class="org.springframework.jdbc.datasource. DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
Define an advice with transaction manager so that all
get
methods will have aread-only
transaction:<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!--all methods starting with 'get' are read-only--> <tx:method name="get*" read-only="true" /> <tx:method name="*" /> </tx:attributes> </tx:advice>
Define the AOP configuration to apply the advice on
pointcut
:<aop:config> <aop:pointcut id="fooServiceOperation" expression="execution(* com.packt.tx.FooService.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation" /> </aop:config> </beans>
Create a test class to get the
FooService
bean and call thegetFoo
method on theFooService
bean. The following is the class:public class TransactionTest { public static void main(String[] args) { AbstractApplicationContext context = new ClassPathXmlApplicationContext( "applicationContextTx.xml"); FooService fooService = (FooService) context.getBean("fooService"); System.out.println(fooService); fooService.getFoo(null); } }
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:- Creating new transaction with name [com.packt.tx.FooServiceImpl.getFoo]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly - Acquired Connection [341280385, URL=jdbc:derby:derbyDB, UserName=dbo, Apache Derby Embedded JDBC Driver] for JDBC transaction - Setting JDBC Connection [341280385, URL=jdbc:derby:derbyDB, UserName=dbo, Apache Derby Embedded JDBC Driver] read-only - Switching JDBC Connection [341280385, URL=jdbc:derby:derbyDB, UserName=dbo, Apache Derby Embedded JDBC Driver] to manual commit - Bound value [org.springframework.jdbc.datasource.ConnectionHolder@6b58ba2b] for key [org.apache.commons.dbcp2.BasicDataSource@680624c7] to thread [main] - Initializing transaction synchronization - Getting transaction for [com.packt.tx.FooServiceImpl.getFoo] - Completing transaction for [com.packt.tx.FooServiceImpl.getFoo] after exception: java.lang.UnsupportedOperationException - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException - Winning rollback rule is: null - No relevant rollback rule found: applying default rules - Triggering beforeCompletion synchronization - Initiating transaction rollback - Rolling back JDBC transaction on Connection [341280385, URL=jdbc:derby:derbyDB, UserName=dbo, Apache Derby Embedded JDBC Driver]
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.
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:
@Transactional(readOnly = true)
public class FooServiceImpl implements FooService {
public Foo getFoo(String fooName) {
}
// This settings has precedence for this method
@Transactional(readOnly = false, propagation =
Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
}
}
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 foraop
andadvice
):<context:annotation-config /> <bean id="fooService" class="com.packt.tx.FooServiceImpl" /> <!-- enable the configuration of transactional behavior based on annotations --> <tx:annotation-driven transaction-manager="txManager" /> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver" /> <property name="url" value="jdbc:derby:derbyDB;create=true" /> <property name="username" value="dbo" /> <property name="password" value="" /> </bean> <bean id="txManager" class="org.springframework.jdbc.datasource .DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
Annotate
FooServiceImpl
with the@Transactional
annotation:@Transactional public class FooServiceImpl implements FooService { @Override public Foo getFoo(String fooName) { throw new UnsupportedOperationException(); } @Override public void insertFoo(Foo foo) { throw new UnsupportedOperationException(); } @Override public void updateFoo(Foo foo) { throw new UnsupportedOperationException(); } }
Create a class called
TransactionTestAnnotation
, loadapplicationContextTxAnnotation
, and examine whether the same log appears. The following is the class:public class TransactionTestAnnotation { public static void main(String[] args) { AbstractApplicationContext context = new ClassPathXmlApplicationContext( "applicationContextTxAnnotation.xml"); FooService fooService = (FooService) context.getBean("fooService"); System.out.println(fooService); fooService.getFoo(null); } }
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).
The following are the characteristics of TransactionTemplate
:
It adopts the same approach as other Spring templates such as
JdbcTemplate
andHibernateTemplate
It uses a callback approach
A
TransactionTemplate
instance is threadsafe
The following code snippet demonstrates TransactionTemplate
with a callback:
Object result = transTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { updateOperation(); return resultOfUpdateOperation(); } });
If there is no return value, use the convenient TransactionCallbackWithoutResult
class via an anonymous class, as follows:
transTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult( TransactionStatus status) { updateOperation1(); updateOperation2(); } });
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.
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
andTransactionStatus
objects, you can initiate transactions and perform a rollback or commit.
The following code snippet provides an example of such use:
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { // execute your business logic here } catch (Exception ex) { txManager.rollback(status); throw ex; } txManager.commit(status);
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.