Spring boot transaction management

What is a transaction

The transaction is a database concept. We can think of transactions as a single unit of work. In a transaction, there can be multiple steps and we have to make sure that either all the steps are successful or none should be successful.

The transaction is having a straight forward rule: DO EVERYTHING OR NOTHING

Use of transaction management

Let’s try to understand better with an example:

Say Suraj wants to transfer Rs.5000 to Naveen’s bank account so what will be the steps?

1 ) Fetch Suraj’s current account details [ Read operation on DB ]

Suraj’s account balance = Rs.10000

2 ) Validate if Suraj’s account is having enough balance to transfer money to Naveen.

Amount to transfer = Rs.5000

Suraj’s account balance = Rs.10000

Validation passes as the account balance is more than the money to transfer.

3 ) Do a debit operation on Suraj’s account [ Write Operation on DB ]

Suraj’s account balance = 10000 – 5000 = 5000

Suraj’s account balance = Rs.5000

4 ) Fetch Naveen’s current account details [ Read operation on DB ]

Naveen’s account balance = Rs.20000

5 ) Do a credit operation on Naveen’s account [ Write Operation on DB ]

Naveen’s account balance = 20000 + 5000 = 25000

Naveen’s account balance = Rs.25000

All the above 5 steps are part of One transaction. Suppose the first 3 steps are complete but failed at 4th Step then what happens?

Data will be inconsistent because debit is done on Suraj’s account but credit did not happen on Naveen’s account.

Suraj’s account balance = 5000

Naveen’s account balance = 20000

So Rs.5000 is lost which we cannot track.

This is the reason transaction came into the picture. Transaction says either commit all the steps or roll back all the steps if we encounter any exception.

If the transaction management was in place in the above example then as soon as an exception was raised at the 4th step then the debit operation on Suraj’s account would have been rolled back to keep the data consistent.

Achieving transaction management

If we are using Spring boot with Spring data dependency then transaction management will be enabled by default and we have to simply make use of @Transactional annotation.

I have already created a sample Spring boot application with an h2 database here Spring boot with H2 database. I have also made use of spring data dependency as part of that project hence transaction management is enabled by default in that application.

On top of that application, I will make some changes to test the transactional behavior.

In the EmployeeServiceImpl class -> saveEmployeeAndManager method we have saved Employee as well as Manager like below

package com.getinputs.h2database.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.getinputs.h2database.dao.EmployeeDao;
import com.getinputs.h2database.model.Employee;
import com.getinputs.h2database.service.ManagerService;
import com.getinputs.h2database.service.EmployeeService;

@Service
public class EmployeeServiceImpl implements EmployeeService {

  @Autowired
  private EmployeeDao employeeDao;

  @Autowired
  private ManagerService managerService;

  @Override
  public String saveEmployeeAndManager() {

    String response = "Employee and Manager Saved.";

    Employee employee = new Employee("Surya", 50000);
    employeeDao.save(employee);

    managerService.saveManager();

    return response;
  }
}

-> mvn clean install -DskipTests

-> mvn spring-boot:run

-> http://localhost:8080/employee

employee-h2-database
manager-h2-database

Data Inconsistency scenario

Say after Saving the Employee details in the above example an exception is raised then what will happen?

Let’s check it, we will update EmployeeServiceImpl class to replicate this scenario

package com.getinputs.h2database.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.getinputs.h2database.dao.EmployeeDao;
import com.getinputs.h2database.model.Employee;
import com.getinputs.h2database.service.ManagerService;
import com.getinputs.h2database.service.EmployeeService;

@Service
public class EmployeeServiceImpl implements EmployeeService {

  @Autowired
  private EmployeeDao employeeDao;

  @Autowired
  private ManagerService managerService;

  @Override
  public String saveEmployeeAndManager() {

    String response = "Employee and Manager Saved.";

    Employee employee = new Employee("Surya", 50000);
    employeeDao.save(employee);

    managerService = null; // Code added
    managerService.saveManager(); // Null pointer Exception is thrown

    return response;
  }
}

We will make the above changes. We don’t have to build and run the application again as we are using the devtools dependency hence build and run happens internally.

-> http://localhost:8080/employee

From the above screenshots, we came to know that Employee details are saved in the database but the Manager details are not saved as a Null pointer exception was raised hence the data is not in a consistent state. In the actual business scenario data inconsistency is not acceptable as it may lead to heavy financial losses.

@Transactional annotation

We are using Spring-boot with Spring-data dependency hence the transaction management is enabled by default. Now when we write @Transactional annotation on the top of a method then this annotation conveys a message to the Spring framework that this particular method should run within a transaction. If the method runs successfully then all the data should be stored in the database but if something fails then no data should be saved.

package com.getinputs.h2database.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.getinputs.h2database.dao.EmployeeDao;
import com.getinputs.h2database.model.Employee;
import com.getinputs.h2database.service.ManagerService;
import com.getinputs.h2database.service.EmployeeService;

@Service
public class EmployeeServiceImpl implements EmployeeService {

  @Autowired
  private EmployeeDao employeeDao;

  @Autowired
  private ManagerService managerService;

  @Override
  @Transactional
  public String saveEmployeeAndManager() {

    String response = "Employee and Manager Saved.";

    Employee employee = new Employee("Surya", 50000);
    employeeDao.save(employee);

    managerService = null;
    managerService.saveManager();

    return response;
  }
}

-> http://localhost:8080/employee

null pointer exception

From the above screenshots, we came to know that neither Employee nor Manager details were saved in the database because an exception was raised in the middle of the transaction. Hence the data is consistent throughout.

https://github.com/getinputs/samples/tree/main/transaction-management

I hope you found this article interesting and valuable. Please share this article with your friends and help me grow. If you are having any concerns or questions about this article please comment below. If you want to get in touch with me please visit the Contact Me page and send an email.

Leave a Comment