Open In App

Spring Boot - Transaction Management Using @Transactional Annotation

Last Updated : 20 Aug, 2025
Comments
Improve
Suggest changes
19 Likes
Like
Report

In enterprise applications, transactions ensure data consistency and integrity. A transaction is a sequence of operations performed as a single logical unit of work. Either all operations within a transaction succeed (commit) or none of them do (rollback).

Spring Boot simplifies transaction management by using the @Transactional annotation, which is built on top of Spring’s declarative transaction management. It frees developers from writing boilerplate code for managing transactions manually.

What is 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;
}

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;
}

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> {
}

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

You 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;
    }
}

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);
    }
}

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());
        }
    }
}

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);
    }
}

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

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.

Suggested Quiz
3 Questions

What is the main purpose of a transaction in an application?

  • A

    Speed up database operations

  • B

    Ensure ACID properties

  • C

    Format SQL queries

  • D

    Encrypt data

Explanation:

Transactions guarantee ACID behavior like atomicity and consistency.

When does Spring automatically roll back a transaction by default?

  • A

    When a checked exception occurs

  • B

    When a runtime exception occurs

  • C

    When a log error occurs

  • D

    Never automatically

Explanation:

Spring rolls back transactions on unchecked (runtime) exceptions.

Which layer is the best place to put @Transactional?

  • A

    Controller layer

  • B

    Repository layer

  • C

    Service layer

  • D

    Model layer

Explanation:

Service layer is ideal because it contains business logic involving multiple DB operations.

Quiz Completed Successfully
Your Score :   2/3
Accuracy :  0%
Login to View Explanation
1/3 1/3 < Previous Next >

Explore