Skip to main content
查利博客

Spring @Transactional Propagation

In the previous article, we have covered some basics of transactional management and the spring @Transactional annotation. In this article, we're going to learn about the transaction Propagation in Spring. Simply put, transaction propagation specifies if any service will or will not participate in transaction and how will it perform if the calling services already have or does not have a transaction created already. In Spring transaction management, it provides seven types of Transaction Propagation. Let's understand all of them one by one with examples. The code for this article is available over on GitHub.

Set-Up #

Application.properties:

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

logging.pattern.console=%msg%n

spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=caizhenhua
spring.datasource.password=123456

REQUIRED #

This is the default propagation mode. It supports a current transaction and create a new one if none exists.

Scenario Service A Service B A calls B
1 REQUIRED REQUIRED B joins existing transaction of A
2 Non-Transactional REQUIRED B creates a new transaction
3 REQUIRED Non-Transactional Similar to Scenario 1

Example 1:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example1 implements CommandLineRunner {
  @Autowired
  ServiceA serviceA;

  @Override
  public void run(String... args) throws Exception {
    serviceA.callServiceB();
  }
}

Service A:

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

@Service
public class ServiceA {
  @Autowired
  ServiceB serviceB;

  @Transactional
  public void callServiceB() {
    System.out.println("ServiceA#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ServiceB {

  @Transactional
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

Creating new transaction with name [com.caizhenhua.springboot.transaction.propagation.required.ServiceA.callServiceB]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Opened new EntityManager [SessionImpl(698477669<open>)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@746fd19b]
ServiceA#callServiceB
Found thread-bound EntityManager [SessionImpl(698477669<open>)] for JPA transaction
Participating in existing transaction
ServiceB#run
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(698477669<open>)]
Closing JPA EntityManager [SessionImpl(698477669<open>)] after transaction

Example 2:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example2 implements CommandLineRunner {
  @Autowired
  ServiceWithoutTx service;

  @Override
  public void run(String... args) throws Exception {
    service.callServiceB();
  }
}

Service A:

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

@Service
public class ServiceWithoutTx {
  @Autowired
  ServiceB serviceB;

  public void callServiceB() {
    System.out.println("ServiceWithoutTx#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ServiceB {

  @Transactional
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

ServiceWithoutTx#callServiceB
Creating new transaction with name [com.caizhenhua.springboot.transaction.propagation.required.ServiceB.run]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Opened new EntityManager [SessionImpl(408583632<open>)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@6aae82cc]
ServiceB#run
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(408583632<open>)]
Closing JPA EntityManager [SessionImpl(408583632<open>)] after transaction

Example 3:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example3 implements CommandLineRunner {
  @Autowired
  ServiceB serviceB;

  @Override
  public void run(String... args) throws Exception {
    serviceB.callServiceWithoutTx();
  }
}

Service A:

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

@Service
public class ServiceB {
  @Autowired
  ServiceWithoutTx service;

  @Transactional
  public void callServiceWithoutTx() {
    System.out.println("ServiceB#callServiceWithoutTx");
    service.run();
  }
}

Service B:

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

@Service
public class ServiceWithoutTx {
  @Autowired
  ServiceB serviceB;

  public void run() {
    System.out.println("ServiceWithoutTx#run");
  }
}

Some Console Output:

Creating new transaction with name [com.caizhenhua.springboot.transaction.propagation.required.ServiceB.callServiceWithoutTx]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Opened new EntityManager [SessionImpl(1685778749<open>)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@7f64bd7]
ServiceB#callServiceWithoutTx
ServiceWithoutTx#run
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(1685778749<open>)]
Closing JPA EntityManager [SessionImpl(1685778749<open>)] after transaction

SUPPORTS #

According to the JavaDoc, propagation SUPPORTS is defined as:

Support a current transaction, execute non-transactionally if none exists.

Scenario Service A Service B A calls B
4 REQUIRED SUPPORTS Same as Scenario 1
5 Non-Transactional SUPPORTS No transaction created

Example 4:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example4 implements CommandLineRunner {
  @Autowired
  ServiceA serviceA;

  @Override
  public void run(String... args) throws Exception {
    serviceA.callServiceB();
  }
}

Service A:

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

@Service(value = "ServiceA_SUPPORTS")
public class ServiceA {
  @Autowired
  ServiceB serviceB;

  @Transactional
  public void callServiceB() {
    System.out.println("ServiceA#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "ServiceB_SUPPORTS")
public class ServiceB {
  @Transactional(propagation = Propagation.SUPPORTS)
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

Creating new transaction with name [com.caizhenhua.springboot.transaction.propagation.supports.ServiceA.callServiceB]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Opened new EntityManager [SessionImpl(864186602<open>)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@4bfe83d]
ServiceA#callServiceB
Found thread-bound EntityManager [SessionImpl(864186602<open>)] for JPA transaction
Participating in existing transaction
ServiceB#run
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(864186602<open>)]
Closing JPA EntityManager [SessionImpl(864186602<open>)] after transaction

Example 5:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example5 implements CommandLineRunner {
  @Autowired
  ServiceWithoutTx service;

  @Override
  public void run(String... args) throws Exception {
    service.callServiceB();
  }
}

Service A:

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

@Service(value = "ServiceWithoutTx_SUPPORTS")
public class ServiceWithoutTx {
  @Autowired
  ServiceB serviceB;

  public void callServiceB() {
    System.out.println("ServiceWithoutTx#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "ServiceB_SUPPORTS")
public class ServiceB {
  @Transactional(propagation = Propagation.SUPPORTS)
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

ServiceWithoutTx#callServiceB
ServiceB#run

MANDATORY #

It supports a current transaction and throw exception if none exist.

Scenario Service A Service B A calls B
6 REQUIRED MANDATORY Same as Scenario 1
7 Non-Transactional MANDATORY Throw exception

Example 6:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example6 implements CommandLineRunner {
  @Autowired
  ServiceA serviceA;

  @Override
  public void run(String... args) throws Exception {
    serviceA.callServiceB();
  }
}

Service A:

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

@Service(value = "ServiceA_MANDATORY")
public class ServiceA {
  @Autowired
  ServiceB serviceB;

  @Transactional
  public void callServiceB() {
    System.out.println("ServiceA#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "ServiceB_MANDATORY")
public class ServiceB {
  @Transactional(propagation = Propagation.MANDATORY)
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

Creating new transaction with name [com.caizhenhua.springboot.transaction.propagation.mandatory.ServiceA.callServiceB]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Opened new EntityManager [SessionImpl(64271451<open>)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@602c167e]
ServiceA#callServiceB
Found thread-bound EntityManager [SessionImpl(64271451<open>)] for JPA transaction
Participating in existing transaction
ServiceB#run
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(64271451<open>)]
Closing JPA EntityManager [SessionImpl(64271451<open>)] after transaction

Example 7:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example7 implements CommandLineRunner {
  @Autowired
  ServiceWithoutTx service;

  @Override
  public void run(String... args) throws Exception {
    service.callServiceB();
  }
}

Service A:

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

@Service(value = "ServiceWithoutTx_MANDATORY")
public class ServiceWithoutTx {
  @Autowired
  ServiceB serviceB;

  public void callServiceB() {
    System.out.println("ServiceWithoutTx#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "ServiceB_MANDATORY")
public class ServiceB {
  @Transactional(propagation = Propagation.MANDATORY)
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

ServiceWithoutTx#callServiceB
Application run failed
java.lang.IllegalStateException: Failed to execute CommandLineRunner
...
Caused by: org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

REQUIRES_NEW #

According to the JavaDoc, propagation REQUIRES_NEW is defined as:

Create a new transaction, and suspend the current transaction if one exists.

Note: REQUIRES_NEW does not work if the calling service and callee are from the same class. This is because the @Transactional is implemented based on the Spring AOP. The proxy pattern only intercept external method calls.

Scenario Service A Service B A calls B
8 REQUIRED REQUIRES_NEW Create a new transaction and suspend the current transaction
9 Non-Transactional REQUIRES_NEW Same as Scenario 2

Example 8:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example8 implements CommandLineRunner {
  @Autowired
  ServiceA serviceA;

  @Override
  public void run(String... args) throws Exception {
    serviceA.callServiceB();
  }
}

Service A:

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

@Service(value = "ServiceA_REQUIRES_NEW")
public class ServiceA {
  @Autowired
  ServiceB serviceB;

  @Transactional
  public void callServiceB() {
    System.out.println("ServiceA#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "ServiceB_REQUIRES_NEW")
public class ServiceB {
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

Creating new transaction with name [com.caizhenhua.springboot.transaction.propagation.requires_new.ServiceA.callServiceB]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Opened new EntityManager [SessionImpl(1160199488<open>)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@c89e263]
ServiceA#callServiceB
Found thread-bound EntityManager [SessionImpl(1160199488<open>)] for JPA transaction
Suspending current transaction, creating new transaction with name [com.caizhenhua.springboot.transaction.propagation.requires_new.ServiceB.run]
Opened new EntityManager [SessionImpl(506594173<open>)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@5059d398]
ServiceB#run
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(506594173<open>)]
Closing JPA EntityManager [SessionImpl(506594173<open>)] after transaction
Resuming suspended transaction after completion of inner transaction
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(1160199488<open>)]
Closing JPA EntityManager [SessionImpl(1160199488<open>)] after transaction

Example 9:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example9 implements CommandLineRunner {
  @Autowired
  ServiceWithoutTx service;

  @Override
  public void run(String... args) throws Exception {
    service.callServiceB();
  }
}

Service A:

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

@Service(value = "ServiceWithoutTx_REQUIRES_NEW")
public class ServiceWithoutTx {
  @Autowired
  ServiceB serviceB;

  public void callServiceB() {
    System.out.println("ServiceWithoutTx#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "ServiceB_REQUIRES_NEW")
public class ServiceB {
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

ServiceWithoutTx#callServiceB
Creating new transaction with name [com.caizhenhua.springboot.transaction.propagation.requires_new.ServiceB.run]: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT
Opened new EntityManager [SessionImpl(246846952<open>)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@c1386b4]
ServiceB#run
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(246846952<open>)]
Closing JPA EntityManager [SessionImpl(246846952<open>)] after transaction

NOT_SUPPORTED #

It does not suuport a current transaction and suspend the current transaction if one exists.

Scenario Service A Service B A calls B
10 REQUIRED NOT_SUPPORTED Suspend the current transaction
11 Non-Transactional NOT_SUPPORTED Same as Scenario 5

Example 10:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example10 implements CommandLineRunner {
  @Autowired
  ServiceA serviceA;

  @Override
  public void run(String... args) throws Exception {
    serviceA.callServiceB();
  }
}

Service A:

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

@Service(value = "ServiceA_NOT_SUPPORTED")
public class ServiceA {
  @Autowired
  ServiceB serviceB;

  @Transactional
  public void callServiceB() {
    System.out.println("ServiceA#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "ServiceB_NOT_SUPPORTED")
public class ServiceB {
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

Creating new transaction with name [com.caizhenhua.springboot.transaction.propagation.not_supported.ServiceA.callServiceB]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Opened new EntityManager [SessionImpl(1205133962<open>)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@76d828ff]
ServiceA#callServiceB
Found thread-bound EntityManager [SessionImpl(1205133962<open>)] for JPA transaction
Suspending current transaction
ServiceB#run
Resuming suspended transaction after completion of inner transaction
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(1205133962<open>)]
Closing JPA EntityManager [SessionImpl(1205133962<open>)] after transaction

Example 11:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example11 implements CommandLineRunner {
  @Autowired
  ServiceWithoutTx service;

  @Override
  public void run(String... args) throws Exception {
    service.callServiceB();
  }
}

Service A:

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

@Service(value = "ServiceWithoutTx_NOT_SUPPORTED")
public class ServiceWithoutTx {
  @Autowired
  ServiceB serviceB;

  public void callServiceB() {
    System.out.println("ServiceWithoutTx#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "ServiceB_NOT_SUPPORTED")
public class ServiceB {
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

ServiceWithoutTx#callServiceB
ServiceB#run

NEVER #

It does not support a current transaction and throw exception if a transaction exists.

Scenario Service A Service B A calls B
12 REQUIRED NEVER Throw an exception

Example 12:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example12 implements CommandLineRunner {
  @Autowired
  ServiceA serviceA;

  @Override
  public void run(String... args) throws Exception {
    serviceA.callServiceB();
  }
}

Service A:

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

@Service(value = "ServiceA_NEVER")
public class ServiceA {
  @Autowired
  ServiceB serviceB;

  @Transactional
  public void callServiceB() {
    System.out.println("ServiceA#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "ServiceB_NEVER")
public class ServiceB {
  @Transactional(propagation = Propagation.NEVER)
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

Application run failed
Caused by: org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

NESTED #

It works like the default propagation mode (REQUIRES) if no active transaction exists. It creates a save point and rollback to it if our business logic code throws an exception. Only some transaction managers support this feature.

Scenario Service A Service B A calls B
13 REQUIRED NESTED Create a save point if the JPA provider supports
14 Non-Transactional NESTED Same as Scenario 2

Example 13:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example13 implements CommandLineRunner {
  @Autowired
  ServiceA serviceA;

  @Override
  public void run(String... args) throws Exception {
    serviceA.callServiceB();
  }
}

Service A:

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

@Service(value = "ServiceA_NESTED")
public class ServiceA {
  @Autowired
  ServiceB serviceB;

  @Transactional
  public void callServiceB() {
    System.out.println("ServiceA#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "ServiceB_NESTED")
public class ServiceB {
  @Transactional(propagation = Propagation.NESTED)
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

Application run failed
Caused by: org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities

By default, Spring Boot Data JPA uses Hibernate as the default JPA vendor. NESTED Transaction is not available on Hibernate.

Example 14:

Driver Class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class Example14 implements CommandLineRunner {
  @Autowired
  ServiceWithoutTx service;

  @Override
  public void run(String... args) throws Exception {
    service.callServiceB();
  }
}

Service A:

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

@Service(value = "ServiceWithoutTx_NESTED")
public class ServiceWithoutTx {
  @Autowired
  ServiceB serviceB;

  public void callServiceB() {
    System.out.println("ServiceWithoutTx#callServiceB");
    serviceB.run();
  }
}

Service B:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "ServiceB_NESTED")
public class ServiceB {
  @Transactional(propagation = Propagation.NESTED)
  public void run() {
    System.out.println("ServiceB#run");
  }
}

Some Console Output:

ServiceWithoutTx#callServiceB
Creating new transaction with name [com.caizhenhua.springboot.transaction.propagation.nested.ServiceB.run]: PROPAGATION_NESTED,ISOLATION_DEFAULT
Opened new EntityManager [SessionImpl(963138052<open>)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@434a8938]
ServiceB#run
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(963138052<open>)]
Closing JPA EntityManager [SessionImpl(963138052<open>)] after transaction