Java > Spring Framework > Spring Data > Spring Transactions
Declarative Transaction Management with Spring Data JPA
This snippet demonstrates declarative transaction management in a Spring Boot application using Spring Data JPA. It covers defining a service method to transfer funds between two accounts and handling potential exceptions within a transactional context.
Core Concepts
Spring's declarative transaction management simplifies transaction handling by using annotations like @Transactional. This annotation demarcates a method as transactional, allowing Spring to automatically handle transaction boundaries (begin, commit, rollback). Spring Data JPA provides repositories that abstract away boilerplate database interaction code.
Entity Definitions (Account)
This defines a simple Account entity with fields like id, accountNumber, and balance. The @Entity annotation marks this class as a JPA entity, and @Id and @GeneratedValue define the primary key.
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String accountNumber;
private double balance;
public Account() {}
public Account(String accountNumber, double balance) {
this.accountNumber = accountNumber;
this.balance = balance;
}
// Getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
Repository Interface (AccountRepository)
This interface extends JpaRepository, providing basic CRUD operations for the Account entity. The findByAccountNumber method is a custom query method that Spring Data JPA will automatically implement based on the method name.
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
Account findByAccountNumber(String accountNumber);
}
Service Layer with Transaction Management
The BankService class handles the fund transfer logic. The @Transactional annotation on the transferFunds method ensures that the entire operation (reading, updating, and saving account balances) is executed within a single transaction. If any exception occurs during the process, the transaction will be rolled back, ensuring data consistency. The custom exception `InsufficientFundsException` extends `RuntimeException` so that the transaction is rolled back when it occurs. Otherwise, if it extended `Exception`, Spring will not automatically roll back the transaction unless explicitly configured to do so.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BankService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transferFunds(String fromAccountNumber, String toAccountNumber, double amount) {
Account fromAccount = accountRepository.findByAccountNumber(fromAccountNumber);
Account toAccount = accountRepository.findByAccountNumber(toAccountNumber);
if (fromAccount == null || toAccount == null) {
throw new IllegalArgumentException("Invalid account numbers.");
}
if (fromAccount.getBalance() < amount) {
throw new InsufficientFundsException("Insufficient funds.");
}
fromAccount.setBalance(fromAccount.getBalance() - amount);
toAccount.setBalance(toAccount.getBalance() + amount);
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
}
@SuppressWarnings("serial")
class InsufficientFundsException extends RuntimeException {
public InsufficientFundsException(String message) {
super(message);
}
}
Real-Life Use Case
This pattern is commonly used in banking applications for transferring funds between accounts, e-commerce platforms for processing payments, and any system that requires maintaining data consistency across multiple operations.
Best Practices
Interview Tip
Be prepared to discuss different transaction isolation levels (READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE) and their impact on data consistency and concurrency. Also, understand the propagation behavior of transactions (REQUIRED, REQUIRES_NEW, SUPPORTS).
When to Use Them
Use declarative transaction management when you need to ensure ACID properties (Atomicity, Consistency, Isolation, Durability) for a series of database operations. It's particularly useful when dealing with complex business logic involving multiple database interactions.
Alternatives
TransactionTemplate or PlatformTransactionManager. This approach offers more flexibility but requires more code.
Pros
Cons
FAQ
-
What happens if an exception is thrown outside the
@Transactionalmethod?
If an exception is thrown outside the@Transactionalmethod, it will not affect the transaction. Only exceptions thrown within the transactional context can trigger a rollback, and only if the exception type is configured to trigger rollback (by default, RuntimeExceptions and Errors). -
How do I configure different transaction isolation levels?
You can configure the transaction isolation level using theisolationattribute of the@Transactionalannotation. For example:@Transactional(isolation = Isolation.READ_COMMITTED). -
What is the default propagation behavior of
@Transactional?
The default propagation behavior isPropagation.REQUIRED. This means that if a transaction already exists, the method will join the existing transaction. If no transaction exists, a new transaction will be created.