Spring Transactions

Spring Transactions provide a robust and flexible mechanism to manage database operations in a consistent and reliable way. Transactions ensure data integrity even in the face of failures or errors.

 

Understanding the Concept of Transactions in Spring

A transaction is a sequence of operations that are treated as a single logical unit.
Key properties of transactions (ACID):

  1. Atomicity: All operations complete successfully, or none do.
  2. Consistency: Ensures that data integrity rules are adhered to.
  3. Isolation: Transactions are executed independently.
  4. Durability: Once completed, changes persist even in case of failures.

Spring provides seamless support for transaction management, simplifying database integration.

Declarative vs Programmatic Transaction Management

Declarative Transaction Management

  • Uses annotations or XML configurations to define transactional boundaries.
  • No explicit transaction management code in the application logic.
  • Example: Using @Transactional annotation.

Programmatic Transaction Management

  • Requires manually writing code to manage transactions (e.g., TransactionTemplate).
  • Provides granular control but is more verbose and error-prone.

Using the @Transactional Annotation

The @Transactional annotation enables declarative transaction management. It can be applied at the class or method level.

@Transactional
public void someTransactionalMethod() {
    // Transactional code here
}

Step-by-Step Guide to Configuring Transactions

a. Maven Dependencies

Add the required dependencies to your pom.xml:

<dependencies>
    <!-- Spring JDBC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.9</version>
    </dependency>
    <!-- MySQL Connector -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>
</dependencies>

c. Creating a Service Class with @Transactional

package com.example.service;

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

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void createUser(String name, String email) {
        String sql1 = "INSERT INTO users (name, email) VALUES (?, ?)";
        jdbcTemplate.update(sql1, name, email);

        // Simulate an error to test rollback
        if (name.equals("error")) {
            throw new RuntimeException("Simulated error");
        }

        String sql2 = "INSERT INTO user_logs (email, action) VALUES (?, ?)";
        jdbcTemplate.update(sql2, email, "User created");
    }
}

d. Writing a Controller to Use the Service

package com.example.controller;

import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/createUser")
    public String createUser(@RequestParam String name, @RequestParam String email) {
        try {
            userService.createUser(name, email);
            return "success";
        } catch (Exception e) {
            return "error";
        }
    }
}

e. Database Schema

Run the following SQL script to set up the database:

CREATE DATABASE springdb;

USE springdb;

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255),
    email VARCHAR(255)
);

CREATE TABLE user_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255),
    action VARCHAR(255)
);

f. JSP Views

success.jsp

<!DOCTYPE html>
<html>
<body>
    <h1>User created successfully!</h1>
</body>
</html>

error.jsp

error.jsp

Running the Application

  • Deploy on Tomcat: Package the project as a WAR file and deploy it to Tomcat.
  • Access the Application:
    • Create a user: http://localhost:8080/{project-name}/createUser?name=John&email=john@example.com
    • Test rollback: http://localhost:8080/{project-name}/createUser?name=error&email=error@example.com

Best Practices for Transaction Management

  • Use @Transactional at Service Layer:
    Keep transactional boundaries at the service layer to separate business logic from the controller.

  • Rollback for Specific Exceptions:
    Configure @Transactional to rollback only for specific exceptions using rollbackFor.

  • Avoid Heavy Logic in Transactions:
    Minimize the amount of code inside transactional methods to reduce the risk of locking.

  • Read-Only Transactions:
    Use @Transactional(readOnly = true) for methods that do not modify the database.