Transaction Propagation and Isolation in Spring @Transactional Annotation

Last Updated : 10 Nov, 2025

In Spring Framework, the @Transactional annotation provides a declarative approach for transaction management. It ensures that operations within a method are executed as a single unit of work, either all succeed or all fail. Understanding transaction propagation and isolation is essential for maintaining data consistency and managing concurrent database operations effectively.

Transaction Propagation and Isolation

Transaction Propagation

Transaction propagation defines how transactions relate to each other when multiple transactional methods are called. Spring supports several propagation behaviors:

  • REQUIRED: The default propagation. If a transaction exists, it will join it; otherwise, a new transaction will be created.
  • REQUIRES_NEW: Suspends the current transaction and creates a new one.
  • SUPPORTS: Runs with a transaction if one exists; otherwise, it runs non-transactionally.
  • NOT_SUPPORTED: Runs the method non-transactionally and suspends any existing transaction.
  • MANDATORY: Requires an existing transaction; throws an exception if none is present.
  • NEVER: Runs without a transaction; throws an exception if one exists.
  • NESTED: Runs within a nested transaction if one exists; otherwise, it starts a new transaction.

Transaction Isolation

Transaction isolation defines how data integrity is maintained when multiple transactions occur concurrently.

  • DEFAULT: Uses the database's default isolation level.
  • READ_UNCOMMITTED: Allows reading uncommitted changes made by other transactions, leading to dirty reads.
  • READ_COMMITTED: Ensures no dirty reads but still allows non-repeatable reads and phantom reads.
  • REPEATABLE_READ: Prevents dirty and non-repeatable reads but allows phantom reads.
  • SERIALIZABLE: The most restrictive level, preventing dirty reads, non-repeatable reads, and phantom reads. Ensures complete isolation but may reduce concurrency.

Implementation of Transaction Propagation and Isolation in Spring Boot

Step 1: Create a New Spring Boot Project

Create a Spring Boot project using Spring Initializr or your IDE.

Project Details:

  • Name: transaction-demo
  • Type: Maven
  • Language: Java
  • Packaging: Jar
  • Java Version: 17
Project Metadata
Project Structure

Step 2: Add the Dependencies

Include the following dependencies in your pom.xml:

XML
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

Project Structure

Ensure your project structure looks similar to the following:

Project Folder Structure
Project Structure

Step 3: Configure application.properties

spring.application.name=transaction-demo

# Database Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/transaction_demo
spring.datasource.username=root
spring.datasource.password=mypassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA and Hibernate
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

Step 4: Create Entities

Order Entity

Java
package com.gfg.transactiondemo.entity;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String orderId;

    public Order(String orderId) {
        this.orderId = orderId;
    }
}

User Entity

Java
package com.gfg.transactiondemo.entity;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;

    public User(String username) {
        this.username = username;
    }
}

Step 5: Create Repositories

OrderRepository

Java
package com.gfg.transactiondemo.repository;

import com.gfg.transactiondemo.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, Long> {
}

UserRepository

Java
package com.gfg.transactiondemo.repository;

import com.gfg.transactiondemo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

Step 6: Service Layer

UserService

Java
package com.gfg.transactiondemo.service;

import com.gfg.transactiondemo.entity.User;
import com.gfg.transactiondemo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.*;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
    public void createUser(User user) {
        userRepository.save(user);
    }
}

This method uses Propagation.REQUIRED, so it will join an existing transaction or start a new one. The isolation level is REPEATABLE_READ, ensuring consistent reads during the transaction.

OrderService

Java
package com.gfg.transactiondemo.service;

import com.gfg.transactiondemo.entity.Order;
import com.gfg.transactiondemo.entity.User;
import com.gfg.transactiondemo.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.*;

@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private UserService userService;

    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
    public void createOrder(Order order, User user) {
        userService.createUser(user);
        orderRepository.save(order);
    }
}

Step 7: Create DTO Class

Java
package com.gfg.transactiondemo.controller;

public class OrderDto {
    private String username;
    private String orderId;

    public OrderDto() {}
    public OrderDto(String username, String orderId) {
        this.username = username;
        this.orderId = orderId;
    }

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

    public String getOrderId() { return orderId; }
    public void setOrderId(String orderId) { this.orderId = orderId; }
}

Step 8: Create Controller

Java
package com.gfg.transactiondemo.controller;

import com.gfg.transactiondemo.entity.*;
import com.gfg.transactiondemo.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("/orders")
    public ResponseEntity<String> createOrder(@RequestBody OrderDto dto) {
        User user = new User(dto.getUsername());
        Order order = new Order(dto.getOrderId());
        orderService.createOrder(order, user);
        return ResponseEntity.ok("Order created successfully");
    }
}

Step 9: Main Application Class

No changes are required in the main class. This is the entry point of the application.

Java
package com.gfg.transactiondemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TransactionDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(TransactionDemoApplication.class, args); // Launches the Spring Boot application
    }
}

Step 10: Run the Application

Run the application using:

mvn spring-boot:run

spring5-compressed
Console output

Step 11: Test the Endpoint

Use a tool like Postman to send a POST request

Endpoint:

POST: http://localhost:8080/createOrder

Request Body and Response:

Postman UI
Request and Response

This demonstrates how transaction propagation and isolation work together when creating a user and order within different transactional contexts.

Comment

Explore