CodeGym /Courses /Module 5. Spring /Rollback and proper use of transactions in Spring

Rollback and proper use of transactions in Spring

Module 5. Spring
Level 6 , Lesson 9
Available

In the previous lecture we covered exception types in transactions and the basic mechanisms for handling them in Spring. Now it's time to dig into practical use and go over more complex rollback scenarios.


Why is it important to configure rollback correctly?

Misconfigured rollbacks can lead to serious issues:

  1. Incomplete changes: some data may remain in an inconsistent state, for example when transferring money between accounts.
  2. Violation of business rules: the system may persist changes that contradict business logic (like an order exceeding the limit).
  3. Scaling problems: in multi-user systems incorrect transaction handling leads to hard-to-track bugs.

Spring provides flexible tools to control transaction rollbacks. Let's learn how to use them.


Practical rollback usage scenarios

In real apps you often run into cases where the default transaction behavior isn't enough. Let's look at an online store example to see how to flexibly manage transactions in different situations.


@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private ProductService productService;

    @Transactional
    public void processOrder(Order order) {
        // Check product availability
        if (!productService.isAvailable(order.getProductId())) {
            // Programmatic rollback: explicitly tell Spring to roll back the transaction
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            throw new ProductNotAvailableException("Product not available");
        }

        // Save the order
        orderRepository.save(order);

        // Check user limits
        if (orderRepository.getUserOrderCount(order.getUserId()) > 5) {
            // Automatic rollback: Spring will roll back the transaction on exception
            throw new BusinessException("Order limit exceeded");
        }

        // Decrease stock and handle special cases
        try {
            productService.decreaseStock(order.getProductId(), order.getQuantity());
        } catch (StockWarningException e) {
            // Log the warning but allow the transaction to complete
            notifyManager(e);
        }
    }

    // Example where some errors shouldn't cancel the whole operation
    @Transactional(noRollbackFor = {StockWarningException.class})
    public void processOrderWithPartialErrors(Order order) {
        orderRepository.save(order);
        productService.decreaseStock(order.getProductId(), order.getQuantity());

        // Even if a StockWarningException occurs,
        // the order will remain saved
    }
}

In this example we see three different approaches to transaction control:

  1. Programmatic rollback via setRollbackOnly() — when we need full control over the process
  2. Automatic rollback on exception — Spring's default behavior for RuntimeException
  3. Selective rollback using noRollbackFor — when some errors shouldn't cancel the whole operation

Each approach has its advantages:

  • Programmatic rollback gives maximum control
  • Automatic rollback keeps the code cleaner and easier to read
  • Selective rollback lets you flexibly handle different types of errors

Testing rollback

To verify transaction behavior, Spring provides handy testing tools. Let's see how we can test our order handling logic:


@SpringBootTest
public class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderRepository orderRepository;

    @Test
    @Transactional
    public void testOrderRollbackOnLimitExceeded() {
        // Setup
        Order order = new Order("Product A", 1);

        // Action
        assertThrows(BusinessException.class, () -> {
            orderService.processOrder(order);
        });

        // Assertion
        assertEquals(0, orderRepository.count(),
            "Database should be empty after transaction rollback");
    }

    @Test
    @Transactional
    public void testPartialRollback() {
        Order order = new Order("Product B", 1);

        // This method does not roll back the transaction for StockWarningException
        orderService.processOrderWithPartialErrors(order);

        // Check that the order was saved despite possible warnings
        assertTrue(orderRepository.existsById(order.getId()));
    }
}

Note:

  • The @Transactional annotation in tests automatically rolls back all changes after each test
  • This lets us avoid worrying about cleaning the database between tests
  • We can easily verify how rollback works in different scenarios

Common mistakes when working with rollback

Working with rollback can be a blessing or a source of problems if you're not careful:

1. Catching exceptions manually: If you catch an exception inside a transactional method, Spring won't know about the error and the transaction will commit successfully.

Example:


@Transactional
public void incorrectTransactionHandling() {
    try {
        saveDataToDatabase();
        throw new RuntimeException("Error");
    } catch (Exception e) {
        // Exception was swallowed, transaction won't roll back!
    }
}

2. Calling a transactional method via this: Spring uses proxies to manage transactions. If you call a transactional method from another method in the same class via this, the @Transactional annotation is ignored.


public class OrderService {
    @Transactional
    public void methodA() {
        // Transaction will work
    }

    public void methodB() {
        this.methodA(); // Transaction won't work
    }
}

Solution: inject the service into itself or move the call to another class.

3. Operations outside a transaction: if you change data outside a transaction, it won't be rolled back. For example, calling an external API or modifying static variables are outside the transaction scope.


Conclusion

Transaction rollback is a powerful mechanism in Spring that helps keep data consistent in case of failures or business errors. But like any tool, rollbacks should be used thoughtfully. I hope you won't be afraid of the word rollback anymore — it's not a bug, it's a lifeline when something goes wrong.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION