Spring Transactional Management
This article will cover the Spring transaction management in detail and go over the @transactional annotation.
Join the DZone community and get the full member experience.
Join For FreeThis article will cover the Spring transaction management in detail. Transaction management is a vast topic, hence I am dividing this article into two sections:
- Spring Transaction management: Understand Spring Transaction management in depth.
- @Transactional annotation: Learn the usage of the annotation and a few caveats of it.
Section 1
Spring Transaction Management
A transaction is a logical unit of work that either completely succeeds or fails. Think about a banking transaction. Here, the unit of work is money debiting from Account A and money crediting to Account B. If one of them fails, the entire process fails. We call it a rollback of all the steps in the transaction if anything fails in between.
Global Transactions: There can be applications (very unlikely) where the transaction can happen between different databases. This is called distributed transaction processing. The transaction manager cannot sit within the application to handle it, rather it sits in the application server level. JTA or java transaction API is required with the support of JNDI to lookup different databases, and the transaction manager decides the commit or rollback of the distributed transaction. This is a complex process and requires knowledge at the application server level.
Local Transactions: Local transactions happen between the application and a singled RDBMS, such as a simple JDBC connection. With local transaction, all the transaction code is within our code.
In both global and local transaction, we have to manage the transaction by ourselves. If I am using JDBC, then the transaction management API is for JDBC. If I am using Hibernate, then the hibernate transaction API and JTA at application server is for global transactions.
Spring framework overcomes all of the above problems by providing an abstraction over the different transaction APIs, providing a consistent programming model. The abstraction is via org.springframework.transaction.PlatformTransactionManager interface. Here is the snippet of the interface:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(
TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
There are various spring managed transaction managers that implement PlatformTransactionManager. Some of them are as follows:
- org.springframework.orm.jpa.JpaTransactionManager — For JPA transactions
- org.springframework.jdbc.datasource.DataSourceTransactionManager — For JDBC transactions
- org.springframework.orm.hibernate5.HibernateTransactionManager — For Hibernate transactions and it binds with SessionFactory
- org.springframework.transaction.jta.JtaTransactionManager — For JTA transactions.
- org.springframework.transaction.jta.WebLogicJtaTransactionManager — For Oracle Weblogic managed transaction
- org.springframework.transaction.jta.WebSphereUowTransactionManager — For IBM Websphere Application Server managed transactions.
- org.springframework.jms.connection.JmsTransactionManager — For JMS messaging transaction by binding JMS connection factory.
Let’s take a use case where we connect to 2 different datasources using JpaTransactionManager and we have Oracle 10g as the database and is managed in a Weblogic server.
The first thing we need to create are 2 datasource beans in spring configuration class like below:
Profile1 for weblogic:
@Bean(name = "dataSource")
@Profile("profile1")
public DataSource jndiDataSource() {
DataSource dataSource = null;
JndiTemplate jndi = new JndiTemplate();
try {
dataSource = (DataSource) jndi.lookup("give_JNDI_LOOKUP_NAME");
} catch (NamingException e) {
LOGGER.error("NamingException for " + jndiName, e);
}
return new AbstractDataSource(dataSource);
}
Profile2 for tomcat server to:
@Bean(name = "dataSource")
@Profile({ "profile2"})
public DataSource jdbcDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
dataSource.setUrl("give db url here ");
dataSource.setUsername("give username");
dataSource.setPassword("give password");
return new AbstractDataSource(dataSource);
}
public class AbstractDataSource extends DelegatingDataSource {
public AbstractDataSource(DataSource delegate) {
super(delegate);
}
@Override
public Connection getConnection() throws SQLException {
return super.getConnection();;
}
}
Create a bean for EntityManager factory:
@Bean(name = "entityManagerFactory")
public EntityManagerFactory getEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceUnitName("ORM_Model");
em.setPersistenceXmlLocation("META-INF/persistence.xml");
em.setDataSource(dataSource);
em.setJpaVendorAdapter(getJpaHibernateVendorAdapter());
em.afterPropertiesSet();
return em.getObject();
}
@Bean
public HibernateJpaVendorAdapter getJpaHibernateVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(true));
adapter.setDatabasePlatform("org.hibernate.dialect.Oracle10gDialect");
return adapter;
}
Define the JPA Transaction Manager:
@Bean(name = "transactionManager")
public JpaTransactionManager getTransactionManager() {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(getEntityManagerFactory());
tm.setDataSource(dataSource);
return tm;
}
Section 2
Spring transactions can be managed by 2 approaches: programmatic and declarative.
Programmatic Approach:
Spring provides a programmatic approach in 2 ways :
- Using the TransactionTemplate
- Using a PlatformTransactionManager implementation directly
The programmatic approach is not widely used, as the transaction management sits with the business logic. In an application where we have transactions for a few CRUD operations, the programmatic approach is preferred as transaction proxies can be a heavy operation.
Declarative Approach (@Transactional )
The declarative approach is widely used because transaction management stays out of business logic. It uses AOP proxies behind to drive transactions around method invocation with appropriate TransactionManager. It can be done either with annotation or with XML. But nowadays, most of the applications are annotation based, so I am covering how it works with the annotations.
1. Use @EnableTransactionManagement
at the top of the configuration class, which has @Configuration
annotation. This is the same as the XML tag:
<tx:annotation-driven transaction-manager="txManager"/>
@Configuration
@EnableTransactionmanagement
public class SpringConfiguration{
...........
...........
}
2. Define the datasource and transaction manager.
@Bean
public FooRepository fooRepository() {
// configure and return a class having @Transactional methods
return new JdbcFooRepository(dataSource());
}
@Bean
public DataSource dataSource() {
// configure and return the necessary JDBC DataSource
}
@Bean
public PlatformTransactionManager txManager() {
return new DataSourceTransactionManager(dataSource());
}
3. Use the @Transactional
annotation above the methods and concrete classes. If applied at class level, all the methods will be by default transactional.
Let's try to understand how the annotation works with a simple example:
Assume we have a sample service lass
Class SampleService {
@Transactional
public void serviceMethod(){
//call to dao layer
}
}
When SampleService is injected in another class, Spring will inject it in the below manner internally:
class ProxySampleService extends SampleService{
private SampleService sampleService;
public ProxySampleService(SampleService s){
this.sampleService=s;
}
@Override
public void sampleMethod(){
try{
//open transaction
sampleService.sampleMethod();
//close transaction
}
catch(Exception e){
//rollback
}
}
}
This is the proxy design that works behind the scenes.
Now let's see how we can fine tune the @Transactional
annotation by changing the setting of the attributes.
Settings of the attributes in @Transactional
annotation:
- propagation — Optional setting for propagation. This is a very important attribute in setting the transactional behavior. I will cover a use case of it below.
- REQUIRED — support a current transaction, create a new one if none exist
- REQUIRES_NEW — create a new transaction and suspend the current transaction if none exist
- MANDATORY — support a current transaction, throw an exception if none exists
- NESTED — executes within a nested transaction if a current transaction exists
- SUPPORTS — supports currents transaction but execute non-transactionally if none exists
- isolation — transaction isolation level. It decides the level to what the transaction should be isolated to other transactions
- DEFAULT — default isolation level of the datasource
- READ_COMMITTED — indicates dirty reads to be prevented, non-repeatable, and phantom reads can occur.
- READ_UNCOMMITTED — indicates that dirty reads, non-repeatable, and phantom reads can occur
- REPEATABLE_READ — indicates dirty and non-repeatable reads are prevented but phantom reads can occur
- SERIALIZABLE — indicates dirty read phantom read, and non-repeatable reads are prevented
- readOnly — whether the transaction is read-only or read/write
- timeout — transaction timeout
- rollbackFor — arrays of exception class objects that must cause a rollback of the transaction
- rollbackForClassName — arrays of exception class names that must cause a rollback of the transaction
- noRollbackFor — arrays of exception class objects that must not cause a rollback of the transaction
- noRollbackForClassName — arrays of exception class names that must not cause a rollback of the transaction
Let's look at a sample to understand how it all works. Our use case is we have a list of employees and we have to update multiple tables for each employee. If there is an exception while processing any employee data, we should roll back all the updated data for that particular employee but other employees should still persist.
public SampleController {
@Autowired
EmployeeService empService;
// Executing multiple task for a list of employees
public void execute (){
public List<Integer> empIdList;//List of employee Id
for (Integer empId:empIdList){
try{
// For each employee id execute a set of task by updating employee
empService.updateEmployee();
}
catch(Exception e){
//Log the employee that has failed .
}
}
}
}
@Transactional
public class EmployeeService {
@Transactional(rollbackFor=Exception.class,propagation= Propagation.REQUIRES_NEW)
public void updateEmployee(){
//dao.task1
//dao2.task2
//dao3.task3 -- exception occurs for 2nd employee
//dao4.task4
}
}
If an exception occurs in task 3 for employee 2, then all the tasks for employee 1 are committed successfully and for employee 2, task 1 and task 2 are not correct as task 3 gave an error. Hence akk the tasks for employee 2 will be rolled back. This is what a transaction managent with transactional settings can help us achieve.
Conclusion
In this article, we have learned what transaction management is, how it operates, the traditional and modern approach, and how spring simplifies the transaction management with the help of annotations.
Published at DZone with permission of Joydip Kumar, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments