Transaction configuration with JPA and Spring 3.1
Join the DZone community and get the full member experience.
Join For FreeThis is the fifth of a series of articles about Persistence with Spring. This article will focus on the configuration of transactions with Spring 3.1 and JPA. For a step by step introduction about setting up the Spring context using Java based configuration and the basic Maven pom for the project, see this article.
The Persistence with Spring series:
- Part 1 – The Persistence Layer with Spring 3.1 and Hibernate
- Part 2 – Simplifying the Data Access Layer with Spring and Java Generics
- Part 3 – The Persistence Layer with Spring 3.1 and JPA
- Part 4 – The Persistence Layer with Spring Data JPA
- Part 5 – Transaction configuration with JPA and Spring 3.1
Spring and Transactions
Spring provides a consistent and comprehensive transaction abstraction over many supported environments. There are two distinct ways of configuring and using transactions – annotations and AOP – each with their own advantages. The reference should provide enough material on both the decision to use the Spring transaction programming model, as well as a in depth discussion of its architecture.
Configuring transactions with annotations
After the introduction of Java based configuration in Spring 3.0, configuring transactions was one of the last things to require XML. Spring 3.1 introduces the new @Enable annotations - @EnableTransactionManagement – which makes it possible to fully use Java for the configuration:
@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 Java is not an option, here is the XML equivalent of this configuration:
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="myEmf" /> </bean> <tx:annotation-driven transaction-manager="txManager" />
For a full discussion and code samples on configuring Spring with JPA, check out a previous article of this series.
The @Transactional configuration
By default, @Transactional will set the propagation to REQUIRED, the readOnly flag to false, and the rollback only for unchecked exceptions. Also note that the isolation level is set to the database default; when using JPA, the isolation level is that of the underlying persistence provider. In the case of Hibernate, the isolation level of all transactions should be REPEATABLE_READ.
For the purpose of this discussion, the relevant application layers will be DAO, Service and Controller. These layers can of course vary from application to application, without changing the underlying principles discussed here.
The @Transactional semantics of the Service and DAO layers should both be configured with REQUIRED propagation and the readOnly flag set to true for the relevant methods. The Controller layer should contain no transaction logic.
Note that this is also the way the DAO implementation is configured in Spring Data. For a detailed analysis of the persistence layer with Spring data, see the previous article of this series.
Assuming a clean separation of layers, where the Controller layer will only invoke the Service layer, which in turn will only call the DAO, then the DAO layer will never be called in a non-transactional context. As such the DAO will never be the transaction owner, and a more strict transaction configuration should be used for it. In this situation, the transactional semantics should be MANDATORY propagation and no readOnly flag. The MANDATORY propagation will simply ensure that a transaction has already been started when the DAO layer is entered, double checking the stated assumption that the DAO is never the transaction owner. The readOnly flag is also not needed because it will be set by the transaction owner as well.
The API layer transaction strategy
With the previous transaction configuration, the Service layer is the transaction owner – it is responsible with starting the transaction and it contains any potential rollback logic. This does however have one downside: a Service write method can invoke another Service write method; because both may contain rollback logic, the transaction owner may not have full control over the rollback logic in some circumstances. For this reason care must be taken if the transaction owner resubmits the transaction or takes corrective action – in these cases the service logic may need to be refactored to avoid this scenario.
The API layer strategy is meant to address this shortcoming by making the Controller layer the transaction owner and working with the assumption that a public Controller method shouldn’t invoke another in any scenario. With this transaction strategy, the DAO and Service transactional semantics remain the same as before; the Controller layer however will use the REQUIRES_NEW propagation to ensure that it invokes the underlying Service layer in a transactional context.
@Transactional( propagation = Propagation.REQUIRES_NEW ) public class Controller{ ... } @Transactional( propagation = Propagation.MANDATORY ) public class Service{ ... } @Transactional( propagation = Propagation.MANDATORY ) public class DAO{ ... }
Spring Testing support for Transactions
The TestContext framework provides transaction support Spring enabled tests via the PlatformTransactionManager bean in the application context and the @Transactional annotation.
Spring also support various test specific annotations which, used in conjunction with the TestContext framework allow for full control over the transaction configuration: @TransactionConfiguration, @Rollback and @BeforeTransaction/@AfterTransaction.
A common usage of the framework and a common requirement of an integration test is to leave the database unchanged after the test method is finished. This can be done by annotating the test class with:
@TransactionConfiguration( defaultRollback = true ) @Transactional public class TransactionalIntegrationTest{ ... }
Also, note that because Spring uses proxies at runtime to manage transactions, the test class must not be final if it’s annotated with @Transactional.
Potential Pitfalls
Changing the Isolation level
One of the major pitfalls when configuring Spring to work with JPA is that changing the isolation of the transaction semantics will not work – JPA does not support custom isolation levels. This is a limitation of JPA, not Spring; nevertheless changing the @Transactionalisolation property will result in:
org.springframework.transaction.InvalidIsolationLevelException: Standard JPA does not support custom isolation levels – use a special JpaDialect for your JPA implementation
Read Only Transactions
The readOnly flag usually generates confusion, especially when working with JPA; from the javadoc:
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 it cannot be guaranteed that an insert or update will not occur when the readOnly flag is set – its behavior is vendor dependent whereas JPA is vendor agnostic.
It is 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 calling 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.
Transaction Logging
Transactional related issues can also be better understood by fine-tuning logging in the transactional packages; the relevant package in Spring is “org.springframework.transaction”, which should be configured with a logging level of TRACE.
Configuring transactions with AOP
In addition to annotations, the declarative transactional configuration can of course make use of the aspect-oriented programming support in Spring. As this is not meant to be an exhaustive guide to all the possible ways to configure transactions in Spring, check out the reference for a in depth discussion on using AOP to configure transactions.
This is a quick starting point to configuring transactions with AOP:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd"> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="true" /> <tx:method name="*" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="serviceOperation" expression="execution(* org.rest.service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation" /> </aop:config> </beans>
Conclusion
This article covered the configuration of the transactional strategies with Spring 3.1 and JPA, using both XML and Java based configuration. Two transaction strategies were discussed, focusing on different ways to manage transaction propagation between the layers of an application. The Spring support for transactional testing as well as some common JPA pitfalls were also discussed. You can check out the full implementation in the github project.
From the originalTransaction configuration with JPA and Spring 3.1 of the Persistence with Spring series
Opinions expressed by DZone contributors are their own.
Comments