Spring Boot @Transactional Example

1. Introduction

The @Transactional annotation in Spring Boot ensures that multiple database operations within a single method either all succeed or all fail. This is crucial in business transactions where, for instance, a payment must be processed for an order to be considered complete.

Key Points:

1. @Transactional enforces atomicity across multiple operations.

2. If any operation within a transactional method fails, the entire transaction is rolled back.

3. This is useful for scenarios such as processing orders with payments where both need to succeed.

2. Implementation Steps

1. Set up a Spring Boot project with JPA and configure the database.

2. Define Order and Payment entities with a relationship.

3. Implement repositories for both entities.

4. Create a service that encapsulates both order processing and payment.

5. Annotate the service method with @Transactional.

6. Write a test to validate the rollback mechanism.

3. Implementation Example

Here is the complete code that demonstrates the usage of @Transactional annotation:
// Define the Order Entity
@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String customer;
    private BigDecimal total;
    // standard getters and setters
}

// Define the Payment Entity
@Entity
public class Payment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long orderId;
    private BigDecimal amount;
    private String status;
    // standard getters and setters
}

// Define the Repositories
public interface OrderRepository extends JpaRepository<Order, Long> {
}

public interface PaymentRepository extends JpaRepository<Payment, Long> {
}

// Implement the Service
@Service
public class OrderProcessingService {
    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private PaymentRepository paymentRepository;

    @Transactional
    public void processOrder(Order order, Payment payment) {
        Order savedOrder = orderRepository.save(order);
        payment.setOrderId(savedOrder.getId());
        Payment savedPayment = paymentRepository.save(payment);
        if (!"SUCCESS".equals(savedPayment.getStatus())) {
            throw new PaymentProcessingException("Payment failed");
        }
    }
}

// Define a custom exception
public class PaymentProcessingException extends RuntimeException {
    public PaymentProcessingException(String message) {
        super(message);
    }
}

// Write a Test to verify rollback on payment failure
@SpringBootTest
@Transactional
public class OrderProcessingServiceTest {

    @Autowired
    private OrderProcessingService orderProcessingService;

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private PaymentRepository paymentRepository;

    @Test
    public void whenPaymentFails_thenRollbackOrderCreation() {
        Order order = new Order();
        order.setCustomer("John Doe");
        order.setTotal(new BigDecimal("100.00"));

        Payment payment = new Payment();
        payment.setAmount(new BigDecimal("100.00"));
        payment.setStatus("FAILED");

        assertThrows(PaymentProcessingException.class, () -> {
            orderProcessingService.processOrder(order, payment);
        });

        assertEquals(0, orderRepository.count());
        assertEquals(0, paymentRepository.count());
    }
}

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

// Run the test class to verify the transactional rollback.

Output:

// The console output should display the result of the test execution, showing that the test method executed and the transaction was rolled back.
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Explanation:

1. Order and Payment are JPA entities representing the business model.

2. OrderRepository and PaymentRepository provide the data access layer for CRUD operations.

3. OrderProcessingService contains a method processOrder that is responsible for saving an order and its corresponding payment transactionally.

4. If the payment status is not "SUCCESS", PaymentProcessingException is thrown, triggering a rollback due to the @Transactional annotation.

5. OrderProcessingServiceTest uses a test case whenPaymentFails_thenRollbackOrderCreation to verify the rollback functionality. The test asserts that if payment processing fails, the creation of the order and payment records should be rolled back, resulting in zero count in the respective repositories.

6. The TransactionalOrderApp class is the Spring Boot main application class but isn't directly related to the test.

7. The output confirms that the transactional behavior is as expected; the test passes with both order and payment counts as zero, indicating a successful rollback.


Comments