Spring Transaction Management

This article will be covering the Spring transaction management in details. Transaction management is a vast topic. Hence I am dividing this article into two sections:

1 . Spring Transaction management: Understand Spring Transaction management in depth.

2.  @Transactional annotation :  This is widely used these days  hence I will explain the usage of the annotation  and few caveats of it.

Section 1 

Spring Transaction management

A transaction is a logical unit of work that either completely succeeds or fails. You can think 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 as 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 database and transaction manager decides the commit or rollback of the distributed transaction. This is a complex process and requires knowledge at 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 transaction management api for jdbc. If I am using Hibernate, then hibernate transaction API and JTA at application server for global transactions.

Spring framework overcomes all the above problems by providing an abstraction over the different transaction API hence 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 manager which implements 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 an use case where we connect to 2 different datasource  using JpaTransactionManager and we have oracle 10g as the database  and is managed in a Weblogic server .

First thing we 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 programmatic approach in 2 ways :

  • Using the TransactionTemplate.
  • Using a PlatformTransactionManager implementation directly.

Programmatic approach is not widely used as the transaction management sits with the business logic . Mostly in application where we have transactions for few CRUD operations , then programmatic approach is preferred as transaction proxies can be a heavy operation .

 Declarative Approach (@Transactional )

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 now a days most of the application 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 is 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 @Transactional   annotation above the methods and concrete classes . If applied at class level , all the methods will be by default transactional . 

Lets try to understand how the annotation works with a simple example:

 Assume we have a sample service class 

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 scene. 

Now lets 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 an 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 one exists.  

        MANDATORY – support a current transaction , throw an exception if none exists .

        NESTED- executes within a nested transaction if a current transaction exist 

        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 – It indicates dirty reads to be prevented, non-repeatable and phantom reads can occur. 

       READ_UNCOMMITTED – It indicates that diry 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 .

USE CASE

Let us take a sample to understand how it all works . Our use case is we have a list of employee and we have to update multiple tables for each employee . If there is any exception while processing any employee data , we should rollback all the updated data for that particular employee but other employee 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 exception occurs in task 3 for employee 2 , then all the task for employee 1 is committed successfully and for employee 2 , task1 and task2 are not correct as task3 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 learnt what is transaction management , how it operates , the traditional and modern approach and how spring simplifies the transaction management with the help of annotations . 

Happy Coding !!

Digiprove sealCopyright secured by Digiprove © 2019 Geeks 18

1 Comment

  1. One of the advantages of using Spring transaction management is that it acts as an abstract layer hiding the underlying transaction management API thus providing a consistent programming model across different transaction APIs such as Java Transaction API (JTA), JDBC, Hibernate, and Java Persistence API (JPA).

Leave a Reply

Your email address will not be published.


*