Spring boot transaction management with NOT_SUPPORTED propagation level

As part of this article, we will try to understand how Spring Boot transaction management works with the NOT_SUPPORTED propagation level.

Propagation level in spring decides the following points:

  • If the spring has to create a new transaction to perform a database operation.
  • Whether the spring can use the same transaction to perform a database operation.
  • If the spring has to throw an exception if the transaction exists.

A transaction is a single unit of work. In a transaction, there can be multiple steps and we have to make sure either all the steps are successful or none should be successful. We know @Transactional annotation helps to achieve transaction management in spring.

Please follow this article before moving further spring boot transaction management here you will understand what is a transaction and how transaction management works in spring boot with a proper example. This article will be an extension of the above-mentioned article. I also suggest you go through REQUIRED propagation level example as we will make use of it in the below example. You can find the link here Spring boot transaction management with required propagation level.

Recap example

NOT_SUPPORTED propagation level

  • If the transaction is not available then do not create any new transaction and run the piece of code without a transaction.
  • If the transaction is available then suspend that transaction and run the piece of code without any transaction.

In our previous example, we have created 2 controllers: EmployeeController and ManagerController. ManagerController saves only Manager. Check the ManagerService below:

package com.getinputs.h2database.service.impl;

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

import com.getinputs.h2database.dao.ManagerDao;
import com.getinputs.h2database.model.Manager;
import com.getinputs.h2database.service.ManagerService;

@Service
public class ManagerServiceImpl implements ManagerService {

	@Autowired
	private ManagerDao managerDao;

	@Override
	public String saveManager() {
		
		String response = "Manager Saved";
		
		Manager manager = new Manager("Suraj");
		managerDao.save(manager);
		
		System.out.println(response);
		return response;
	}
}

We will update the above method with NOT_SUPPORTED propagation.

package com.getinputs.h2database.service.impl;

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

import com.getinputs.h2database.dao.ManagerDao;
import com.getinputs.h2database.model.Manager;
import com.getinputs.h2database.service.ManagerService;

@Service
public class ManagerServiceImpl implements ManagerService {

	@Autowired
	private ManagerDao managerDao;

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED)
	public String saveManager() {
		
		String response = "Manager Saved";
		
		Manager manager = new Manager("Suraj");
		managerDao.save(manager);
		
		System.out.println(response);
		return response;
	}
}

We will update the application.properties with log4j properties so as to print the transaction logs.

application.properties

# DB Properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# Logging Properties
logging.level.ROOT=INFO
logging.level.org.springframework.orm.jpa=DEBUG
logging.level.org.springframework.transaction=DEBUG

Once we complete the above changes we will build and run the application.

-> mvn clean install -DskipTests

-> mvn spring-boot:run

Trigger http://localhost:8080/manager

Output: Manager Saved

2022-05-29 09:56:27.330  INFO 12008 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-05-29 09:56:27.331  INFO 12008 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-05-29 09:56:27.332  INFO 12008 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2022-05-29 09:56:27.350 DEBUG 12008 --- [nio-8080-exec-1] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2022-05-29 09:56:27.362 DEBUG 12008 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(640657898<open>)] for JPA transaction
2022-05-29 09:56:27.376 DEBUG 12008 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(640657898<open>)] for JPA transaction
2022-05-29 09:56:27.376 DEBUG 12008 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2022-05-29 09:56:27.379 DEBUG 12008 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@3ea923cf]
2022-05-29 09:56:27.412 DEBUG 12008 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2022-05-29 09:56:27.412 DEBUG 12008 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(640657898<open>)]
2022-05-29 09:56:27.423 DEBUG 12008 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Not closing pre-bound JPA EntityManager after transaction
2022-05-29 09:56:27.424 DEBUG 12008 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Resuming suspended transaction after completion of inner transaction
Manager Saved
2022-05-29 09:56:27.442 DEBUG 12008 --- [nio-8080-exec-1] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
2022-05-29 09:56:27.531 DEBUG 12008 --- [nio-8080-exec-2] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2022-05-29 09:56:27.533 DEBUG 12008 --- [nio-8080-exec-2] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
2022-05-29 09:56:27.538 DEBUG 12008 --- [nio-8080-exec-2] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2022-05-29 09:56:27.593 DEBUG 12008 --- [nio-8080-exec-2] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor

From the above logs, we can conclude that a new transaction is created in the JPA repository. No new transaction is created at the Service level hence the object is saved in the database without any transaction.

Case 1: When no transaction exists

1 ) Request reaches the ManagerController and it makes a call to saveManager method.

2 ) The saveManager method in ManagerController makes a call to saveManager method of ManagerServiceImpl.

3 ) Spring realizes the saveManager method is annotated with @Transactional annotation and Propagation.NOT_SUPPORTED. Now spring checks if there is an existing transaction. In this case, there is no existing transaction hence there is no need to suspend any transaction and no new transaction is created.

4 ) The saveManager method will run without any transaction and the manager object is saved in the database without any transaction.

Now we will check a case when the transaction already exists. For that, we will make the changes in the EmployeeServiceImpl. EmployeeController saves Employee as well as Manager. Check the below service method.

@Override
public String saveEmployeeAndManager() {
  String response = "Employee and Manager Saved.";
  Employee employee = new Employee("Surya", 50000);
  employeeDao.save(employee);
  managerService.saveManager(); // This code calls Manager service method
  return response;
}

We will update the above method with the Required propagation.

package com.getinputs.h2database.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
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(propagation = Propagation.REQUIRED)
	public String saveEmployeeAndManager() {
		
		String response = "Employee and Manager Saved.";
		
		Employee employee = new Employee("Surya", 50000);
		employeeDao.save(employee);
		managerService.saveManager();
		
		return response;
	}
}

Once we complete the above changes we will build and run the application.

-> mvn clean install -DskipTests

-> mvn spring-boot:run

Trigger http://localhost:8080/employee

Output: Employee and Manager Saved.

2022-05-29 10:33:16.887 DEBUG 12008 --- [nio-8080-exec-5] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2022-05-29 10:33:16.888 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(787937005<open>)] for JPA transaction
2022-05-29 10:33:16.888 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [com.getinputs.h2database.service.impl.EmployeeServiceImpl.saveEmployeeAndManager]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2022-05-29 10:33:16.889 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@20311d43]
2022-05-29 10:33:16.895 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(787937005<open>)] for JPA transaction
2022-05-29 10:33:16.895 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Participating in existing transaction
2022-05-29 10:33:16.899 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(787937005<open>)] for JPA transaction
2022-05-29 10:33:16.899 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Suspending current transaction
2022-05-29 10:33:16.899 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2022-05-29 10:33:16.900 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(462501024<open>)] for JPA transaction
2022-05-29 10:33:16.900 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@56d15b5]
2022-05-29 10:33:16.901 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2022-05-29 10:33:16.901 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(462501024<open>)]
2022-05-29 10:33:16.903 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(462501024<open>)] after transaction
2022-05-29 10:33:16.903 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Resuming suspended transaction after completion of inner transaction
Manager Saved
2022-05-29 10:33:16.903 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Resuming suspended transaction after completion of inner transaction
2022-05-29 10:33:16.903 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2022-05-29 10:33:16.903 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(787937005<open>)]
2022-05-29 10:33:16.903 DEBUG 12008 --- [nio-8080-exec-5] o.s.orm.jpa.JpaTransactionManager        : Not closing pre-bound JPA EntityManager after transaction
2022-05-29 10:33:16.905 DEBUG 12008 --- [nio-8080-exec-5] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor

From the above logs, we come to know a new transaction TRANSACTION-1 is created at EmployeeService and the employee object is saved under TRANSACTION-1. But as saveManager method in ManagerService is annotated with NOT_SUPPORTED hence TRANSACTION-1 gets suspended and the manager object is saved in the database without any transaction.

Case 2: When a transaction exists

1 ) Request reaches the EmployeeController and makes a call to the saveEmployeeAndManager method.

2 ) saveEmployeeAndManager method in EmployeeController makes a call to saveEmployeeAndManager method of EmployeeServiceImpl.

3 ) Spring realizes the saveEmployeeAndManager method is annotated with @Transactional annotation and Propagation.REQUIRED. Here Spring checks if there is an existing transaction available but as of now there is no transaction available hence spring creates a new transaction [ TRANSACTION-1 ]

4 ) Employee object is saved in the database under TRANSACTION-1.

5 ) saveEmployeeAndManager method of EmployeeServiceImpl makes a call to saveManager method of ManagerServiceImpl.

6 ) Spring realizes the saveManager method is also annotated with @Transactional annotation and Propagation.NOT_SUPPORTED. Here Spring checks if there is an existing transaction available. Now spring comes to know there is already a transaction available TRANSACTION-1 hence it suspends the TRANSACTION-1 and it saves the manager object with no transaction.

7 ) Once the manager object is saved with no transaction then again the suspended transaction TRANSACTION-1 will be resumed.

You can find the complete project here https://github.com/getinputs/samples/tree/main/transaction-management-not-supported-propagation

In this article, we have covered a Sample application on how the spring boot transaction management works with the NOT_SUPPORTED Propagation level. 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