Spring boot transaction management with NEVER propagation level

As part of this article, we will try to understand how Spring Boot transaction management works with the NEVER 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

NEVER 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 throw an IllegalTransactionStateException.

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 NEVER 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.NEVER)
  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-06-18 10:08:54.695  INFO 9036 --- [  restartedMain] c.g.h2database.H2databaseApplication     : Started H2databaseApplication in 2.31 seconds (JVM running for 3.194)
2022-06-18 10:08:58.999  INFO 9036 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-06-18 10:08:58.999  INFO 9036 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-06-18 10:08:59.000  INFO 9036 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2022-06-18 10:08:59.011 DEBUG 9036 --- [nio-8080-exec-1] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2022-06-18 10:08:59.018 DEBUG 9036 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(471694493<open>)] for JPA transaction
2022-06-18 10:08:59.027 DEBUG 9036 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(471694493<open>)] for JPA transaction
2022-06-18 10:08:59.027 DEBUG 9036 --- [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-06-18 10:08:59.030 DEBUG 9036 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@25b2ebec]
2022-06-18 10:08:59.054 DEBUG 9036 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2022-06-18 10:08:59.054 DEBUG 9036 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(471694493<open>)]
2022-06-18 10:08:59.062 DEBUG 9036 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Not closing pre-bound JPA EntityManager after transaction
2022-06-18 10:08:59.062 DEBUG 9036 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Resuming suspended transaction after completion of inner transaction
Manager Saved
2022-06-18 10:08:59.074 DEBUG 9036 --- [nio-8080-exec-1] 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 and the code gets executed without a 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 @Transactional annotation and Propagation.NEVER. Now spring checks if there is an existing transaction. In this case, there is no existing transaction. Spring does not create any new transactions.

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

There was an unexpected error (type=Internal Server Error, status=500).
Existing transaction found for transaction marked with propagation 'never'
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.handleExistingTransaction(AbstractPlatformTransactionManager.java:413)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:352)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:595)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:382)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
	at com.getinputs.h2database.service.impl.ManagerServiceImpl$$EnhancerBySpringCGLIB$$c1f9361d.saveManager(<generated>)
	at com.getinputs.h2database.service.impl.EmployeeServiceImpl.saveEmployeeAndManager(EmployeeServiceImpl.java:30)
	at com.getinputs.h2database.service.impl.EmployeeServiceImpl$$FastClassBySpringCGLIB$$9161643f.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

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 NEVER and a transaction already exists hence an exception will be thrown.

Case 2: When a transaction exists

1 ) Request reaches the EmployeeController and makes a call to 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.NEVER. 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 throws an IllegalTransactionStateException and the manager object is not saved.

https://github.com/getinputs/samples/tree/main/transaction-management-never-propagation
In this article, we have covered a Sample application on how the spring boot transaction management works with the NEVER 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