Spring Boot - Transaction Management Using @Transactional Annotation

Last Updated : 7 Apr, 2026

Transactions ensure that multiple database operations are executed as a single unit, maintaining data consistency and integrity. In Spring Boot, transaction management is simplified using @Transactional, which automatically handles commit and rollback operations.

  • Ensures all-or-nothing execution (commit or rollback) of operations
  • Uses @Transactional to automate transaction handling
  • Eliminates manual transaction management code

Transaction Management

Transaction management is the process of coordinating database operations to follow the ACID properties:

  1. Atomicity: All or nothing.
  2. Consistency: Database remains valid before and after transaction.
  3. Isolation: Concurrent transactions do not affect each other.
  4. Durability: Changes persist even after system failures.

Example: In a banking system, if you transfer money:

  • Debit amount from one account.
  • Credit amount to another account.

Both operations should succeed or fail together.

@Transactional Annotation

The @Transactional annotation:

  • Automatically starts a transaction when a method is called.
  • Commits the transaction if the method completes successfully.
  • Rolls back the transaction if a runtime exception occurs.
  • Reduces boilerplate transaction-handling code.

Note: If you use spring-boot-starter-data-jpa, Spring Boot auto-configures transaction management, so @EnableTransactionManagement isn’t required. Add it only if you’re not using JPA starter or need custom transaction management.

Configure Transaction in Spring Boot

In this example, we will create an application to store user information along with his address information and will use spring transaction management to resolve the transaction break problem.

Step-by-Step Implementation of Transaction Management

Step 1: Create A Spring Boot Project

Create a Spring Boot project using Spring Initializr. Select the following:

  • Project: Maven
  • Spring Boot: 3.x
  • Dependencies: Spring Web, Spring Data JPA, MySQL Driver, Lombok

Step 2: Add Dependencies

We will add the required dependencies for our spring boot application.

Add Dependencies

Step 3: Configure Database

Now, we will configure the database in our application. We will be using the following configurations and add them to our application.properties file.

server.port=9090

# Database Configuration

spring.datasource.url=jdbc:mysql://localhost:3306/employee_db

spring.datasource.username=root

spring.datasource.password=root

# JPA Configuration

spring.jpa.hibernate.ddl-auto=update

spring.jpa.show-sql=true

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect

Note: Please add your database username & password along with the database path.

Step 4: Create Model Classes

We will create two model classes: Employee and Address with a @OneToOne relationship.

Employee.java

Java
package com.example.transactionmanagement.model;

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

@Entity
@Table(name = "emp_info")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Employee {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    private String name;
}

Explanation: Represents the Employee entity and maps it to the database with a one-to-one relationship with Address.

Address.java

Java
package com.example.transactionmanagement.model;

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

@Entity
@Table(name = "add_info")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Address {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String address;
  
    // One-to-one mapping: one employee has one address
    @OneToOne
    private Employee employee;
}

Explanation: Represents the Address entity linked to Employee using a one-to-one mapping.

Step 5: Create Repository Layer

In this step, we will create a Repository Layer. For this, we will be creating EmployeeRepository and AddressRepository and will be extending JpaRepository<T, ID> for performing database-related queries.

EmployeeRepository.java

Java
package com.example.transactionmanagement.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import com.example.transactionmanagement.model.Employee;

public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
}

Explanation: Provides CRUD operations for Employee using Spring Data JPA.

AddressRepository.java

Java
package com.example.transactionmanagement.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import com.example.transactionmanagement.model.Address;

public interface AddressRepository extends JpaRepository<Address, Long> {
}

Step 6: Create a Service Layer

We can use @Transactional annotation in service layer which will result interacting with the database. In this step, we will create a service layer for our application and add business logic to it. For this, we will be creating two classes EmployeeService and AddressService. In EmployeeService class we are throwing an exception.

EmployeeService.java

Java
package com.example.transactionmanagement.service;

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

import com.example.transactionmanagement.model.Address;
import com.example.transactionmanagement.model.Employee;
import com.example.transactionmanagement.repository.EmployeeRepository;

@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;
    
    @Autowired
    private AddressService addressService;
    
    @Transactional(rollbackFor = Exception.class)
    public Employee addEmployee(Employee employee) throws Exception {
        Employee employeeSavedToDB = this.employeeRepository.save(employee);
        
        // Create address for employee
        Address address = new Address();
        address.setAddress("Varanasi");
        address.setEmployee(employee);
        
        // This may throw an exception intentionally for testing rollback
        if (employee.getName().equalsIgnoreCase("error")) {
            throw new RuntimeException("Simulated Exception: Forcing rollback!");
        }
        
        this.addressService.addAddress(address);
        return employeeSavedToDB;
    }
}

Explanation: Contains business logic and uses @Transactional to ensure atomic operations on Employee and Address.

AddressService.java

Java
package com.example.transactionmanagement.service;

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

import com.example.transactionmanagement.model.Address;
import com.example.transactionmanagement.repository.AddressRepository;

@Service
public class AddressService {
    
    @Autowired
    private AddressRepository addressRepository;
    
    public Address addAddress(Address address) {
        return this.addressRepository.save(address);
    }
}

Explanation: Handles business logic related to Address operations..

Step 7: Create Controller

In this step, we will create a controller for our application. For this, we will create a Controller class and add all the mappings to it.

Controller.java

Java
package com.example.transactionmanagement.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import com.example.transactionmanagement.model.Employee;
import com.example.transactionmanagement.service.EmployeeService;

@RestController
@RequestMapping("/api/employee")
public class EmployeeController {
    
    @Autowired
    private EmployeeService employeeService;
    
    @PostMapping("/add")
    public ResponseEntity<?> saveEmployee(@RequestBody Employee employee) {
        try {
            Employee employeeSaved = employeeService.addEmployee(employee);
            return ResponseEntity.ok(employeeSaved);
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("Transaction failed: " + e.getMessage());
        }
    }
}

Explanation: Exposes REST APIs to handle client requests for Employee and Address operations

Step 8: Enable Transaction Management in Main Class

TransactionManagementApplication.java

Java
package com.example.transactionmanagement;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement
public class TransactionManagementApplication{

    public static void main(String[] args) {
        SpringApplication.run(TransactionManagementApplication.class, args);
    }
}

Explanation: This is the main Spring Boot class that starts the application and enables transaction management using @EnableTransactionManagement

Step 9: Running Our Application

In this step, we will run our application. Once, we run our application using hibernate mapping in our database required tables will be created.

Output_screen

Explanation: Spring Boot is auto-generating the database schema and setting up relationships when the application starts.

As we can see in logs, our table has been created. We can also confirm it by looking at our database.

Database_logs

Step 10: Test the Application

  1. Use Postman to send a request:POST http://localhost:9090/api/employee/add
  2. Body (JSON):

{

"name": "ankur"

}

Adding Employee_postman

As we can see in the above response we have added an employee. We can also check our database for employee data and address data.

Employee Data_in_database

Similarly, we can also check for address data.

Address Data_in_database

Problem Without Transaction Management

If we don’t use transaction management, partial data may persist in the database even if an error occurs.

Example: In the EmployeeService class, we deliberately initialize the Address object as null. This will cause a NullPointerException when we try to save the address, but the employee record will still be saved in the database.

Java
@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Autowired
    private AddressService addressService;

    public Employee addEmployee(Employee employee) throws Exception {
        Employee employeeSavedToDB = this.employeeRepository.save(employee);

        // Address object initialized as null
        Address address = null; 
        address.setId(123L); // This will throw NullPointerException
        address.setAddress("Varanasi");
        address.setEmployee(employee);

        // Save address
        this.addressService.addAddress(address);

        return employeeSavedToDB;
    }
}

Now, we will delete our table from the database and again run our application and will request the application to create an employee.

As we can see in the above media file, we have initialized the address object as null and requested the application, we have an employee created in the database but the address information is not, as we have received a null pointer exception. But, this is not good practice in transaction management, as employees should be saved only when the address is saved and vice-versa.

Applying Transaction Management

To solve this, we use @Transactional.

  • If any exception occurs, both Employee and Address will roll back.
  • Add @EnableTransactionManagement in the main class.

TransactionManagementApplication.java

Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement
public class TransactionManagementApplication {

    public static void main(String[] args) {
        SpringApplication.run(TransactionManagementApplication.class, args);
    }
}

Update EmployeeService:

Java
@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Autowired
    private AddressService addressService;

    @Transactional(rollbackFor = Exception.class)
    public Employee addEmployee(Employee employee) throws Exception {
        Employee employeeSavedToDB = this.employeeRepository.save(employee);

        // Intentional null to simulate failure
        Address address = null;
        address.setId(123L);
        address.setAddress("Varanasi");
        address.setEmployee(employee);

        this.addressService.addAddress(address);

        return employeeSavedToDB;
    }
}

Running Application

1. Run the Spring Boot application.

2. When making a request to add an employee:

  • Without @Transactional : Employee saved, Address failed.
  • With @Transactional: Both Employee and Address rolled back (nothing saved).

Note: This ensures data consistency: either both tables are updated or neither is updated.

As we can see in the above media file, this time the employee data do not get stored in the database, nor did the address data. This way the spring has handled the transaction that both employees and address data gets stored or no data gets stored.

Comment

Explore