Transactionality

πŸ›  Understanding Transactions with Multiple Domain Objects in Spring Data JPA

To grasp transaction boundaries, rollback scenarios, and failure handling, let’s build an example using two related domain objects: Student and CourseRegistration.


πŸ”Ή Scenario:

We will create a service where:
βœ… A student registers for a course.
βœ… Both Student and CourseRegistration are saved within the same transaction.
βœ… If anything goes wrong (like a database constraint violation), the entire transaction rolls back.


πŸ“Œ Step 1: Define Domain Entities

1️⃣ Student Entity

package com.example.model;

import jakarta.persistence.*;

@Entity
@Table(name = "students")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String firstName;
    private String lastName;
    private String email;

    public Student() {}

    public Student(String firstName, String lastName, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    // Getters and Setters
}

2️⃣ CourseRegistration Entity

package com.example.model;

import jakarta.persistence.*;

@Entity
@Table(name = "course_registrations")
public class CourseRegistration {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @ManyToOne
    @JoinColumn(name = "student_id")
    private Student student;

    private String courseName;

    public CourseRegistration() {}

    public CourseRegistration(Student student, String courseName) {
        this.student = student;
        this.courseName = courseName;
    }

    // Getters and Setters
}

πŸ“Œ Step 2: Create Repositories

1️⃣ StudentRepository

package com.example.repository;

import com.example.model.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends JpaRepository<Student, Integer> {
}

2️⃣ CourseRegistrationRepository

package com.example.repository;

import com.example.model.CourseRegistration;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CourseRegistrationRepository extends JpaRepository<CourseRegistration, Integer> {
}

πŸ“Œ Step 3: Implement Transactional Service

package com.example.service;

import com.example.model.CourseRegistration;
import com.example.model.Student;
import com.example.repository.CourseRegistrationRepository;
import com.example.repository.StudentRepository;
import jakarta.transaction.Transactional;
import org.springframework.stereotype.Service;

@Service
public class RegistrationService {

    private final StudentRepository studentRepository;
    private final CourseRegistrationRepository courseRegistrationRepository;

    public RegistrationService(StudentRepository studentRepository, CourseRegistrationRepository courseRegistrationRepository) {
        this.studentRepository = studentRepository;
        this.courseRegistrationRepository = courseRegistrationRepository;
    }

    @Transactional
    public void registerStudentForCourse(String firstName, String lastName, String email, String courseName) {
        Student student = new Student(firstName, lastName, email);
        studentRepository.save(student);

        // Simulating an error (uncomment to test rollback)
        // if (true) throw new RuntimeException("Something went wrong!");

        CourseRegistration registration = new CourseRegistration(student, courseName);
        courseRegistrationRepository.save(registration);
    }
}

πŸ“ Explanation

  • The method registerStudentForCourse(...) is annotated with @Transactional, ensuring that both Student and CourseRegistration are saved within the same transaction.
  • If an exception occurs, the transaction rolls back, and neither object is saved.

πŸ“Œ Step 4: Test Transaction Handling

package com.example;

import com.example.service.RegistrationService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

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

    @Bean
    public CommandLineRunner demo(RegistrationService registrationService) {
        return args -> {
            try {
                System.out.println("Registering student...");
                registrationService.registerStudentForCourse("John", "Doe", "john.doe@example.com", "Spring Boot");

                // Uncomment to simulate failure and rollback
                // registrationService.registerStudentForCourse("Jane", "Doe", "jane.doe@example.com", "Spring Boot");
                System.out.println("Student successfully registered!");
            } catch (Exception e) {
                System.out.println("Transaction failed: " + e.getMessage());
            }
        };
    }
}

πŸ“Œ Step 5: Simulating Rollback

βœ… Successful Transaction

If everything works fine, both Student and CourseRegistration are saved.

πŸ›  Database Records

StudentCourse Registration
John DoeSpring Boot

❌ Failed Transaction & Rollback

If we uncomment the exception in registerStudentForCourse(), the entire transaction rolls back, and no records are saved.

πŸ“ Why?

  • @Transactional ensures atomicity: either all changes succeed or none are applied.

πŸ“Œ Key Takeaways

ConceptExplanation
@TransactionalEnsures all database operations are atomic (all succeed or all fail).
RollbackIf an exception occurs, Spring undoes all changes automatically.
PropagationDetermines how transactions interact when calling methods within other transactions.
Checked vs. Unchecked ExceptionsBy default, unchecked exceptions (RuntimeException) trigger a rollback, while checked exceptions (Exception) do not.

πŸš€ Next Steps

βœ… Explore Propagation Types (e.g., REQUIRED, REQUIRES_NEW, NESTED)
βœ… Handle Checked Exceptions in transactions

πŸ‘‰ Would you like to explore different transaction propagation behaviors next? 😊