Enterprise Java application servers natively provide JTA (Java Transaction API) support, which enables distributed transaction, which is also known as global transaction, spanning multiple resources, applications and servers. Traditionally, Enterprise Java Beans (EJB) and Message Driven Beans (MDB) were used for container-managed transactions (CMT), which is based on JTA and JNDI. JTA transaction management is resource-intensive; its exception handling is based on checked exceptions and so is not developer-friendly. Moreover, unit testing is hard with EJB CMT.
For those who do not want to use resource-intensive JTA transactions, a local transaction is another available option, and one that allows you to programmatically enforce resource-specific transactions using APIs such as JDBC. Although relatively easy to use, it is limited to a single resource, as multiple resources cannot participate in a single transaction. Moreover, local transactions are often invasive, hence they pollute your code.
Spring Transaction abstraction solves the problems of global and local transactions by providing a consistent transaction model that can run in any environment. Although it supports both declarative and programmatic transaction management, the declarative model is sufficient for most cases. Spring Transaction eliminates the need for an application server such as JBoss or WebLogic just for transactions. You can start with local transactions using Spring on a simple Servlet engine such as Tomcat and scale it up later to distributed transactions on an application server without touching your business code, just by changing the transaction manager in your Spring metadata.
Most applications just need local transactions since they do not deal with multiple servers or transactional resources such as databases, JMS, and JCA; hence, they do not need a full-blown application server. For distributed transactions spanned across multiple servers over remote calls, you need JTA, necessitating an application server, as JTA needs JNDI to look up the data source. JNDI is normally available only in an application server. Use JTATransactionManager
inside application servers for JTA capabilities.
When you deploy your Spring application inside an application server, you can use server-specific transaction managers to utilize their full features. Just switch the transaction manager to use server-specific JtaTransactionManager
implementations such as WebLogicJTATransactionManager
and WebSphereUowTransactionManager
inside your Spring metadata. All your code is completely portable now.
Spring Transaction Management abstraction is designed around an interface named PlatformTransactionManager
, which you need to configure as a Spring bean in your Spring metadata. PlatformTransactionManager
manages the actual transaction instance that performs the transaction operations such as commit and rollback, based on a TransactionDefinition
instance that defines the transaction strategy. TransactionDefinition
defines the critical transaction attributes such as isolation, propagation, transaction timeout, and the read-only status of a given transaction instance.
Transaction attributes determine the behavior of transaction instances. They can be set programmatically as well as declaratively. Transaction attributes are:
Isolation level: Defines how much a transaction is isolated from (can see) other transactions running in parallel. Valid values are: None
, Read committed
, Read uncommitted
, Repeatable reads
, and Serializable
. Read committed
cannot see dirty reads from other transactions.
Propagation: Determines the transactional scope of a database operation in relation to other operations before, after, and nested inside itself. Valid values are: REQUIRED
, REQUIRES_NEW
, NESTED
, MANDATORY
, SUPPORTS
, NOT_SUPPORTED
, and NEVER
.
Timeout: Maximum time period that a transaction can keep running or waiting before it completes. Once at timeout, it will roll back automatically.
Read-only status: You cannot save the data read in this mode.
These transaction attributes are not specific to Spring, but reflect standard transactional concepts. The TransactionDefinition
interface specifies these attributes in the Spring Transaction Management context.
Depending on your environment (standalone, web/app server) and the persistence mechanism you use (such as plain JDBC, JPA, and Hibernate), you choose the appropriate implementation of PlatformTransactionManager
and configure it as required, in your Spring metadata. Under the hood, using Spring AOP, Spring injects TransactionManager
into your proxy DAO (or EntityManager
, in the case of JPA) and executes your transactional methods, applying transaction semantics declared in your Spring configuration, either using the @Transactional
annotation or the equivalent XML notations. We will discuss the @Transactional
annotation and its XML equivalent later on in this chapter.
For applications that operate on a single DataSource
object, Spring provides DataSourceTransactionManager
. The following shows how to configure it in XML:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="taskifyDS" /> </bean>
For multiple DataSource
objects or transactional resources, you need a JtaTransactionManager
with JTA capabilities, which usually delegates to a container JTA provider. You need to use DataSource
objects in Java EE application servers, defined with the server, and looked up via JNDI along with JtaTransactionManager
. A typical combination should look like the following code fragment:
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" /> </bean> <jee:jndi-lookup id="taskifyDS" jndi-name="java:jboss/datasources/taskify" expected-type="javax.sql.DataSource/>
If you are using Hibernate and just a single DataSource
(and no other transactional resource), then the best option is to use HibernateTransactionManager
, which requires you to pass the session factory as a dependency. For JPA, Spring provides JpaTransactionManager
, which binds a single JPA EntityManager
instance. However, it is advisable to use JtaTransactionManager
in application-container environments.
Spring provides specialized transaction managers for application servers for WebLogic and WebSphere in order to leverage full power from container-specific transaction coordinators. Use WebLogicTransactionManager
and WebsphereUowTransactionManager
in the respective environments.
Separating Transaction semantics out of your business code into an XML file or annotations above the methods is usually called declarative transaction management. Spring Framework allows you to apply transactional behavior into your beans transparently and non-invasively using its declarative transaction management feature.
You can apply Spring Transaction declaratively on any Spring bean, unlike EJB CMT. With Spring Transaction, you can specify transactional advices around your bean methods inside the metadata in an AOP style; then Spring will apply your those advices at runtime using AOP. You can set rollback rules to specify which exceptions around which beans or methods cause automatic rollback or non-rollback.
Spring Transactions supports two transactional modes: proxy mode and AspectJ mode. Proxy is the default and most popular mode. In proxy mode, Spring creates an AOP proxy object, wrapping the transactional beans, and applies transactional behavior transparently around the methods using transaction aspects based on the metadata. The AOP proxy created by Spring based on transactional metadata, with the help of the configured PlatformTransactionManager
, performs transactions around the transactional methods.
If you choose AspectJ mode for transactions, the transactional aspects are woven into the bean around the specified methods modifying the target class byte code during compile-time. There will be no proxying in this case. You will need AspectJ mode in special cases such as invoking transactional methods of the same class with different propagation levels, where proxying would not help.
Spring offers two convenient approaches for declaratively defining the transactional behavior of your beans:
@Transactional
annotationLet's start with AOP configuration in an XML file. Refer to the Aspect Oriented Programming section of Chapter 1, Getting Started with Spring Core, for a detailed discussion of configuring AOP, using aspects, pointcuts, advice, and so on.
Typically, you declare transaction advices and pointcuts with pointcut expressions in your XML metadata file. The best approach is to keep the transaction configuration in a separate bean-definition file (for example, transation-settings.xml
) and import it into your primary application-context file.
Typically, you declare transactional advices and other semantics as shown in the following code:
<!-- transactional advices --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- the transactional semantics... --> <tx:attributes> <!-- all methods starting with 'get' are read-only --> <tx:method name="find*" read-only="true" /> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <!-- Applying the above advices to the service layer methods --> <aop:config> <aop:pointcut id="allServiceMethods" expression="execution(* com.taskify.service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut- ref="allServiceMethods" /> </aop:config>
You can see that this AOP configuration instructs Spring how to weave transactional advices around the methods using pointcuts. It instructs TransactionManager
to make all find methods of the entire service layer read-only, and to force other methods to have the transaction propagation: REQUIRED
, which means that, if the caller of the method is already in a transactional context, this method joins the same transaction without creating a new one; otherwise, a new transaction is created. If you want to create a different transaction for this method, you should use the REQUIRES_NEW
propagation.
Also, note that the transaction isolation level is specified as DEFAULT
, which means the default isolation of the database is to be used. Most databases default to READ_COMMITTED
, which means a transactional thread cannot see the data of other transactions in progress (dirty reads).
With Spring transaction, you can set rollback rules declaratively, in the same <tx:advice>
block, as shown in the following code:
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> ... <tx:method name="completeTask" propagation="REQUIRED" rollback-for="NoTaskFoundException"/> <tx:method name="findOpenTasksByAssignee" read-only="true" no-rollback-for="InvalidUserException"/> <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" /> </tx:attributes> </tx:advice>
You can specify which exceptions should or should not rollback transactions for your business operations using the rollback-for
and no-rollback-for
attributes of the <tx:method>
element.
TransactionException
thrown by the PlatformTransactionManager
interface's methods is the unchecked exception, RuntimeException
. In Spring, transactions rollback for unchecked exceptions automatically. Checked, or application exceptions are not rolled back unless specified in the metadata, using the rollback-for
attribute.
Spring Transaction allows you to customize the transactional behavior of your beans to a minute level of granularity using Spring AOP and SpEL. Moreover, you can specify the behavioral attributes of your transaction such as propagation, isolation, and timeout at the method level on the <tx:method>
element.
The @Transactional
annotation describes transactional attributes on a method or class. Class-level annotation applies to all methods unless explicitly annotated at method level. It supports all the attributes you otherwise set at the XML configuration. See the following example:
@Service @Transactional public class TaskServiceImpl implements TaskService { ... public Task createTask(Task task) { if (StringUtils.isEmpty(task.getStatus())) task.setStatus("Open"); taskDAO.save(task); return task; } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = NoUserFoundException) public Task createTask(String name, int priority, Long createdByuserId, Long assigneeUserId, String comments) { Task task = new Task(name, priority, "Open", userService.findById(createdByuserId), null, userService.findById(assigneeUserId), comments); taskDAO.save(task); logger.info("Task created: " + task); return task; } @Transactional(readOnly = true) public Task findTaskById(Long taskId) { return taskDAO.findOne(taskId); } ... }
In the preceding example, the transactional method createTask
with propagation REQUIRED
rolls back for NoUserFoundException
. Similarly, you can set no-rollback rules at the same level too.
@Transactional
can be applied only to public methods. If you want to annotate over protected, private, or package-visible methods, consider using AspectJ, which uses compile-time aspect weaving. Spring recommends annotating @Transactional
only on concrete classes as opposed to interfaces, as it will not work in most cases such as when you use proxy-target-class="true"
or mode="aspectj"
.
You need to first enable transaction management in your application before Spring can detect the @Transactional
annotation for your bean methods. You enable transaction in your XML metadata using the following notation:
<tx:annotation-driven transaction-manager="transactionManager" />
The following is the Java configuration alternative for the preceding listing:
@Configuration @EnableTransactionManagement public class JpaConfiguration { }
Spring scans the application context for bean methods annotated with @Transactional
when it sees either of the preceding settings.
You can change the transaction mode from proxy
, which is the default, to aspectj
at this level:
<tx:annotation-driven transaction-manager="transactionManager" mode="aspectj"/>
Another attribute you can set at this level is proxy-target-class
, which is applicable only in the case of the proxy
mode.
Spring provides comprehensive support for programmatic transaction management using two components: TransactionTemplate
and PlatformTransactionManager
. The following code snippet illustrates the usage of TransactionTemplate
:
@Service public class TaskServiceImpl implements TaskService { @Autowired private TransactionTemplate trxTemplate; ... public Task createTask(String name, int priority, Long createdByuserId, Long assigneeUserId, String comments) { return trxTemplate.execute(new TransactionCallback<Task>() { @Override public Task doInTransaction(TransactionStatus status) { User createdUser = userService.findById(createdByuserId); User assignee = userService.findById(assigneeUserId); Task task = new Task(name, priority, "Open", createdUser, null, assignee, comments); taskDAO.save(task); logger.info("Task created: " + task); return task; } }); } }
TransactionTemplate
supports the setting of all transaction attributes, as in the case of XML configuration, which gives you more granular control at the expense of mixing your business code with transactional concerns. Use it only if you need absolute control over a particular feature that cannot be achieved with declarative transaction management. Use declarative transaction management if possible, for better maintainability and management of your application.