-
Book Overview & Buying
-
Table Of Contents
Mockito for Spring
By :
Spring Framework provides supports for transaction management. The following are characteristics of the Spring transaction management framework:
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:
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.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.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:
public interface TransactionDefinition {
int getIsolationLevel();
int getPropagationBehavior();
String getName();
int getTimeout();
boolean isReadOnly();
}ISOLATION_DEFAULTISOLATION_READ_COMMITTEDISOLATION_READ_UNCOMMITTEDISOLATION_REPEATABLE_READISOLATION_SERIALIZABLEPROPAGATION_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 timeoutThe 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:
setRollbackOnly() call within a transaction context if necessaryWe'll create a simple Spring transaction management project and learn about the basics. The following are the steps to create the project:
Foo, under the com.packt.tx package. The following is the class body:package com.packt.tx;
public class Foo {
}FooService, to handle the CRUD operations on Foo:package com.packt.tx;
public interface FooService {
Foo getFoo(String fooName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}FooService, and from each method, throw UnsupportedOperationException 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();
}
}applicationContextTx.xml directly under the src 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 a read-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>FooService bean and call the getFoo method on the FooService 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);
}
}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:
PROPAGATION_REQUIREDISOLATION_DEFAULTRuntimeException will trigger a rollback and any checked exception will not trigger a rollbackWhen 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:
applicationContextTxAnnotation.xml and add the following lines (no need for aop and advice):<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>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();
}
}TransactionTestAnnotation, load applicationContextTxAnnotation, 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:
TransactionTemplatePlatformTransactionManager implementation directlyThe 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:
JdbcTemplate and HibernateTemplateTransactionTemplate instance is threadsafeThe 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:
PlatformTransactionManager to your bean via a bean reference.TransactionDefinition and TransactionStatus 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);
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.
Change the font size
Change margin width
Change background colour