Transactions with Spring and JPA
1. Overview
This tutorial will discuss the right way to configure Spring Transactions, how to use the @Transactional annotation and common pitfalls.
For a more in-depth discussion on the core persistence configuration, check out the Spring with JPA tutorial.
Basically, there are two distinct ways to configure Transactions – annotations and AOP – each with their own advantages. We’re going to discuss the more common annotation-config here.
Further reading:
Configuring Separate Spring DataSource for Tests
A quick, practical tutorial on how to configure a separate data source for testing in a Spring application.
Quick Guide on Loading Initial Data with Spring Boot
A quick and practical example of using data.sql and schema.sql files in Spring Boot.
Show Hibernate/JPA SQL Statements from Spring Boot
Learn how you can configure logging of the generated SQL statements in your Spring Boot application.
2. Configure Transactions Without XML
Spring 3.1 introduces the @EnableTransactionManagement annotation that we can use in a @Configuration class and enable transactional support:
@Configuration
@EnableTransactionManagement
public class PersistenceJPAConfig{
@Bean
public LocalContainerEntityManagerFactoryBean
entityManagerFactoryBean(){
//...
}
@Bean
public PlatformTransactionManager transactionManager(){
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
entityManagerFactoryBean().getObject() );
return transactionManager;
}
}
However, if we’re using a Spring Boot project, and have a spring-data-* or spring-tx dependencies on the classpath, then transaction management will be enabled by default.
3. Configure Transactions with XML
Before 3.1 or if Java is not an option, here is the XML configuration, using annotation-driven and the namespace support:
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="myEmf" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
4. The @Transactional Annotation
With transactions configured, we can now annotation a bean with @Transactional either at the class or method level:
@Service
@Transactional
public class FooService {
//...
}
The annotation supports further configuration as well:
-
the Propagation Type of the transaction
-
the Isolation Level of the transaction
-
a Timeout for the operation wrapped by the transaction
-
a readOnly flag – a hint for the persistence provider that the transaction should be read only
-
the Rollback rules for the transaction
Note that – by default, rollback happens for runtime, unchecked exceptions only. The checked exception does not trigger a rollback of the transaction. We can, of course, configure this behavior with the rollbackFor and noRollbackFor annotation parameters.
5. Potential Pitfalls
At a high level, Spring creates proxies for all the classes annotated with @Transactional – either on the class or on any of the methods. The proxy allows the framework to inject transactional logic before and after the running method – mainly for starting and committing the transaction.
What’s important to keep in mind is that, if the transactional bean is implementing an interface, by default the proxy will be a Java Dynamic Proxy. This means that only external method calls that come in through the proxy will be intercepted. Any self-invocation calls will not start any transaction, even if the method has the @Transactional annotation.
Another caveat of using proxies is that only public methods should be annotated with @Transactional. Methods of any other visibilities will simply ignore the annotation silently as these are not proxied.
This article discusses further proxying pitfalls in great detail here.
5.2. Changing the Isolation Level
@Transactional(isolation = Isolation.SERIALIZABLE)
Note that this has actually been introduced in Spring 4.1; if we run the above example before Spring 4.1, it will result in:
org.springframework.transaction.InvalidIsolationLevelException: Standard JPA does not support custom isolation levels – use a special JpaDialect for your JPA implementation
5.3. Read-Only Transactions
This just serves as a hint for the actual transaction subsystem; it will not necessarily cause failure of write access attempts. A transaction manager which cannot interpret the read-only hint will not throw an exception when asked for a read-only transaction.
The fact is that we can’t be sure that an insert or update will not occur when the readOnly flag is set. This behavior is vendor dependent, whereas JPA is vendor agnostic.
It’s also important to understand that the readOnly flag is only relevant inside a transaction. If an operation occurs outside of a transactional context, the flag is simply ignored. A simple example of that would call a method annotated with:
@Transactional( propagation = Propagation.SUPPORTS,readOnly = true )
from a non-transactional context – a transaction will not be created and the readOnly flag will be ignored.
6. Conclusion
We covered the basic configuration of transactional semantics using both Java and XML, how to use @Transactional and best practices of a Transactional Strategy.
As always, the code presented in this article is available over on Github.