Using @Transactional with Spring Boot and JPA

Burak KILINC
turkcell

--

When working with databases in a Spring Boot application, it’s crucial to manage transactions properly. The @Transactional annotation is a powerful tool that simplifies this process. In this article, we'll explore how to use @Transactional with Spring Boot and JPA.

Understanding Transactions

A transaction is a set of operations that should either all succeed or all fail. For instance, when transferring money between two bank accounts, it’s essential that both the withdrawal from one account and the deposit into the other account happen together.

Setting Up Your Project

Make sure you have Spring Boot and a database set up in your project. You can use Spring Initializer (https://start.spring.io/) to generate a new project with the required dependencies.

Adding JPA Repository

Create a JPA entity (e.g., User) and a corresponding repository interface (e.g., UserRepository) that extends JpaRepository. This will give you access to basic CRUD operations.

@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;

// Getters and setters
}

public interface UserRepository extends JpaRepository<User, Long> {
// Custom queries can be defined here if needed
}

Using @Transactional

Let’s say you have a service class (e.g., UserService) where you want to perform some business logic involving multiple database operations.

@Service
public class UserService {

@Autowired
private UserRepository userRepository;

@Transactional
public void performBusinessLogic(User user) {
userRepository.save(user);
// Additional database operations...

// If an exception occurs here, the entire transaction will be rolled back
}
}

Explaining @Transactional

  • @Transactional is placed on top of the method that should be transactional.
  • If any exception occurs during the method execution, the entire transaction is rolled back (i.e., all changes made within the transaction are undone).
  • If the method completes successfully, the transaction is committed.

Understanding Transactional Propagation in Spring Boot

In addition to using @Transactional, it's important to understand transaction propagation when working with Spring Boot and JPA. Transaction propagation defines the behavior of a method that is already running within a transaction context when another method is invoked.

Types of Transactional Propagation

  1. REQUIRED (Default):
  • If there is an existing transaction, the method will participate in that transaction. If there is no existing transaction, a new transaction will be created.

2. REQUIRES_NEW:

  • This always starts a new transaction. If there is an existing transaction, it will be suspended until this new transaction completes.

3. SUPPORTS:

  • If there is an existing transaction, the method will participate in that transaction. If there is no existing transaction, the method will execute non-transactionally.

4. MANDATORY:

  • Requires an existing transaction. If there is no existing transaction, an exception will be thrown.

5. NOT_SUPPORTED:

  • Executes non-transactionally. If there is an existing transaction, it will be suspended until this method completes.

6. NEVER:

  • Requires that no transaction exists. If an existing transaction is found, an exception will be thrown.

7. NESTED:

  • Creates a nested transaction. If there is no existing transaction, it behaves like REQUIRED.

Examples of Transactional Propagation

Let’s consider an example where we have two methods, methodA() and methodB(), both annotated with @Transactional:

Testing the Transaction

You can write a test to see @Transactional in action:

@SpringBootTest
class UserServiceTest {

@Autowired
private UserService userService;

@Test
void testBusinessLogic() {
User user = new User();
user.setUsername("john_doe");
user.setEmail("john@example.com");

userService.performBusinessLogic(user);

// Verify that the user was saved to the database
Optional<User> savedUser = userRepository.findById(user.getId());
assertTrue(savedUser.isPresent());
}
}

In this example:

  • methodA() starts a new transaction (if none exists) and saves a new user (Alice). It then calls methodB().
  • methodB() always starts a new transaction and saves a new user (Bob). If methodB() is called within an existing transaction, that transaction is suspended until methodB() completes.

Conclusion

Understanding transaction propagation is crucial for building robust applications with Spring Boot and JPA. By choosing the right propagation type, you can ensure that your transactions behave as expected and maintain data integrity.

Remember to carefully design your transactions based on the requirements of your application to achieve the desired behavior.

Happy coding!

--

--