Sunday, September 17, 2023

Difference between @Transactional and @EnableTransactionManagement in Spring Boot

Any enterprise program that deals with data storage and retrieval must incorporate transaction management. Annotations like @Transactional and @EnableTransactionManagement in Spring make it simpler to manage transactions. Although both of these annotations support transaction management, there are some important distinctions between them that programmers must be aware of in order to use them properly. The distinctions between @Transactional and @EnableTransactionManagement in Spring will be covered in this post, along with examples to guide your decision-making.

But before diving into it, we would like to introduce you with a few important terms. Let's first define them in the context of Spring and why it's important. 

What is meant by transaction? 

Transactions are used to manage database operations in Spring, and they ensure that data remains consistent and reliable. A transaction on a database level is a logical unit of work that includes one or more database operations, ensuring data integrity in the face of concurrent access and failure scenarios. 


@Transactional Annotation

The @Transactional annotation is used to mark a method as transactional. When a method is marked as transactional, Spring will automatically manage the transaction for that method. The transactional behaviour can be customised using various attributes of the @Transactional annotation.

Some of the attributes of the @Transactional annotation are:

propagation: Specifies how the transaction should propagate to other methods. For example, if a transactional method calls another transactional method, the propagation attribute determines how the transactions are managed. There exist various types of propagation. These include REQUIRED (Default), REQUIRES_NEW, SUPPORTS, etc.


Difference between @Transactional and @EnableTransactionManagement in Spring


isolation: specifies the transaction's level of isolation. The interaction of the transaction with other transactions is determined by this.

Let’s understand the annotation with the help of real world examples:

Think about a situation where one of our service methods needs to store some data in a database. We want to make sure that this operation is atomic, which means that it only performs one of the two outcomes: success or failure. Here is an illustration of how we could accomplish this using the @Transactional annotation:

@Service

public class PlayerService {

    @Autowired

    private PlayerRepository playerRepository;

    @Transactional

    public void savePlayer(Player player) {

        playerRepository.save(player);

    }

}

In this example, the savePlayer method is annotated with @Transactional. This means that when the method is called, a transaction will be created around the method's execution. If an exception is thrown during the execution of the method, the transaction will be rolled back, meaning any changes made to the database will be undone.

Examples of different propagation attributes:

Let's now look at some examples of how @Transactional can be utilized with various propagation properties.

Propagation.REQUIRED: This propagation attribute is used by default. It suggests that the method should start a new transaction if there isn't one already, or join an existing one if there is. Here's an illustration:

@Service

public class UserService {

@Autowired

private UserRepository userRepository;

@Transactional(propagation = Propagation.REQUIRED)

    public void saveUser(User user) {

        userRepository.save(user);

        saveUserRole(user);

    }

    @Transactional(propagation = Propagation.REQUIRED)

    public void saveUserRole(User user) {

        // save user role

    }

}

In this example, the saveUser method calls the saveUserRole method, which is also annotated with @Transactional(propagation = Propagation.REQUIRED). Since both methods have the same propagation attribute, they will participate in the same transaction.

Propagation.REQUIRES_NEW: This propagation attribute indicates that a new transaction should always be created for the method, suspending any existing transaction if there is one. Here's an example:

@Service

public class UserService {

    @Autowired

    private UserRepository userRepository;

    @Transactional(propagation = Propagation.REQUIRED)

    public void saveUser(User user) {

        userRepository.save(user);

        saveUserRole(user);

    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    public void saveUserRole(User user) {

        // save user role

    }

}


Annotated with @Transactional(propagation = Propagation.REQUIRES_NEW), the saveUser function in this example calls the saveUserRole method. This indicates that a different transaction, separate from the one used by the saveUser method, will be generated for the saveUserRole method.


@EnableTransactionManagement

To enable transaction management in your Spring application, use the @EnableTransactionManagement annotation. The @Transactional annotation on methods will be immediately detected by Spring when you apply it, enabling transaction management for those methods.

At the configuration level, the @EnableTransactionManagement annotation is frequently used. It provides transaction management for all application beans when applied to the main application class.

Let’s understand with example below:

Suppose we have a simple Spring application that manages cars. We have a CarRepository interface that defines methods for adding, deleting, and retrieving cars, as well as a CarService class that implements these methods. We want to ensure that these methods are executed within a transaction, so we'll use @Transactional.

First, we'll define our CarRepository interface:

public interface CarRepository {

    void save(Car car);

    void delete(Car car);

    Car getById(int id);

    List<Car> getAll();

}

Next, we'll define our CarService class:

@Service

public class CarService {

    private final CarRepository carRepository;

    public CarService(CarRepository carRepository) {

        this.carRepository = carRepository;

    }

    @Transactional

    public void addCar(Car car) {

        carRepository.save(car);

    }

    @Transactional

    public void deleteCar(Car car) {

        carRepository.delete(car);

    }

    @Transactional(readOnly = true)

    public Car getCarById(int id) {

        return carRepository.getById(id);

    }

    @Transactional(readOnly = true)

    public List<Car> getAllCars() {

        return carRepository.getAll();

    }

}

Notice that we've annotated each method that needs to be executed within a transaction with @Transactional. We've also specified readOnly = true for the getCarById and getAllCars methods, since they only read data and don't modify it.

Now, we need to enable transaction management for our application. We'll create a configuration class called AppConfig and annotate it with @EnableTransactionManagement:

@Configuration

@EnableTransactionManagement

public class AppConfig {

    @Bean

    public DataSource dataSource() {

        // configure and return data source

    }

    @Bean

    public PlatformTransactionManager transactionManager() {

        return new DataSourceTransactionManager(dataSource());

    }

    @Bean

    public CarRepository carRepository() {

        // create and return carRepository implementation

    }

    @Bean

    public CarService carService() {

        return new CarService(carRepository());

    }

}


In this example, we've defined beans for our data source, transaction manager, carRepository, and carService. We've also annotated the class with @EnableTransactionManagement, which tells Spring to enable transaction management for our application.

With this configuration in place, Spring will automatically create a transaction for any method annotated with @Transactional when it's called. If an exception is thrown within the transaction, Spring will automatically roll back the transaction.


Differences between @Transactional and @EnableTransactionManagement in Spring Framework

Now that we are familiar with both the annotations. It’s time to take a look at the difference between the two. Here are some of the key differences between @Transactional and @EnableTransactionManagement:

Scope

The @Transactional annotation is used to mark a single method as transactional, while the @EnableTransactionManagement annotation is used to enable transaction management for an entire application or a configuration.

Customization

While the @EnableTransactionManagement annotation allows you to customise transaction management behaviour at the configuration level, the @Transactional annotation allows you to customise transactional behaviour of a particular method using a variety of characteristics.

Detection

When you use the @EnableTransactionManagement annotation, Spring automatically identifies @Transactional annotations on methods. Spring won't detect @Transactional annotations without the @EnableTransactionManagement annotation.

Transaction Manager

 The @Transactional annotation uses the default transaction manager, while the @EnableTransactionManagement annotation allows you to specify the transaction manager to be used for transactional methods.

Flexibility

The @Transactional annotation is more flexible, as it allows you to customize the transactional behavior of a single method in great detail. The @EnableTransactionManagement annotation, on the other hand, provides a more centralized approach to transaction management.


Conclusion

In conclusion, @Transactional and @EnableTransactionManagement are both important annotations in Spring that allow us to manage transactions in our applications. @Transactional is used to annotate a specific method to indicate that it should be executed within a transaction, while @EnableTransactionManagement is used to enable transaction management for our entire application.

The main difference between the two is that @Transactional is more fine-grained and allows us to specify individual transactional behaviors for each method, while @EnableTransactionManagement provides a broader configuration for transaction management. 

By using these annotations appropriately, we can ensure that our Spring applications are well-designed and reliable.

I hope this article was able to shed some light on the differences between @Transactional and @EnableTransactionManagement annotations in Spring. By using these annotations correctly, we can make sure our transactions are managed properly and our applications are more reliable.


1 comment:

  1. I have one question, slightly unrelated but relevant, does Spring Data supports Transaction management? and if I have to choose between Hibernate and Spring Data JPA which should I choose and why?

    ReplyDelete