🚀 Introduction: What is @Transactional
in Spring Boot?
The @Transactional
annotation in Spring Boot is used to manage database transactions automatically. It ensures that database operations such as save, update, and delete are executed as a single unit, maintaining data integrity.
✅ Key Features of @Transactional
:
✔ Ensures atomicity (all operations succeed or fail together).
✔ Rolls back transactions automatically on exceptions.
✔ Supports different propagation behaviors (nested, mandatory, required).
✔ Works with Spring Data JPA and Hibernate.
📌 In this guide, you’ll learn:
✅ How @Transactional
works in Spring Boot.
✅ How to use it for rollback, propagation, and isolation levels.
✅ Best practices for writing transactional code.
1️⃣ Why Use @Transactional
?
Consider a scenario where you need to save two related entities in a database. If the second save operation fails, the first one should also be rolled back to maintain data consistency.
📌 Example: Without @Transactional
, Inconsistent Data May Occur
public void registerUser(String name, String email) {
userRepository.save(new User(name, email)); // Saved successfully
if (email.contains("invalid")) {
throw new RuntimeException("Invalid email!"); // Exception occurs
}
logRepository.save(new UserLog(email, "User registered")); // Not executed
}
📌 Problem:
- The
User
record is saved before the exception occurs, but theUserLog
entry is not saved. - This results in inconsistent database state.
✅ Solution: Wrap the entire method with @Transactional
to ensure rollback.
2️⃣ Basic Example: Using @Transactional
for Atomicity
📌 Example: Ensuring Atomicity with @Transactional
1. Service Layer (UserService.java
)
@Service
public class UserService {
private final UserRepository userRepository;
private final UserLogRepository userLogRepository;
public UserService(UserRepository userRepository, UserLogRepository userLogRepository) {
this.userRepository = userRepository;
this.userLogRepository = userLogRepository;
}
@Transactional // Ensures both operations are successful or rolled back
public void registerUser(String name, String email) {
userRepository.save(new User(name, email));
if (email.contains("invalid")) {
throw new RuntimeException("Invalid email!"); // Forces rollback
}
userLogRepository.save(new UserLog(email, "User registered"));
}
}
2. Repository Layer (UserRepository.java
)
public interface UserRepository extends JpaRepository<User, Long> {
}
📌 Scenario 1: Valid Email (registerUser("Ramesh", "ramesh@example.com")
)
✅ User and log entry are both saved.
📌 Scenario 2: Invalid Email (registerUser("Suresh", "invalid-email")
)
❌ User and log entry are both rolled back (data remains consistent).
✅ Ensures database consistency by rolling back all changes when an exception occurs.
3️⃣ @Transactional
and Rollback Behavior
By default, @Transactional
rolls back on unchecked exceptions (RuntimeException
, Error
), but not on checked exceptions (Exception
).
📌 Example: Rollback on Checked Exceptions (SQLException
)
@Transactional(rollbackFor = Exception.class) // Ensures rollback for all exceptions
public void registerUser(String name, String email) throws SQLException {
userRepository.save(new User(name, email));
if (email.contains("invalid")) {
throw new SQLException("Database error!"); // Checked exception
}
userLogRepository.save(new UserLog(email, "User registered"));
}
✅ Ensures rollback for both checked and unchecked exceptions.
4️⃣ Understanding @Transactional
Propagation Levels
Propagation determines how transactions interact when multiple methods with @Transactional
are called.
Propagation Type | Behavior |
---|---|
REQUIRED (Default) |
Uses an existing transaction or creates a new one if none exists. |
REQUIRES_NEW |
Always creates a new transaction, suspending the current one. |
MANDATORY |
Must run within an existing transaction, otherwise throws an exception. |
NESTED |
Runs within a nested transaction inside the existing one. |
SUPPORTS |
Uses the current transaction if available, otherwise runs without one. |
NOT_SUPPORTED |
Executes the method outside of any transaction. |
📌 Example: Using REQUIRES_NEW
to Ensure Logs Are Always Saved
@Transactional
public void registerUser(String name, String email) {
userRepository.save(new User(name, email));
try {
saveUserLog(email);
} catch (Exception e) {
// Log the error but do not affect user registration
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveUserLog(String email) {
userLogRepository.save(new UserLog(email, "User registered"));
}
✅ If registerUser
fails, UserLog
is still saved because it runs in a separate transaction.
5️⃣ Setting Isolation Levels for Concurrency Control
Isolation levels prevent dirty reads, non-repeatable reads, and phantom reads in concurrent transactions.
Isolation Level | Behavior |
---|---|
READ_COMMITTED (Default) |
Prevents dirty reads but allows non-repeatable reads. |
REPEATABLE_READ |
Prevents dirty and non-repeatable reads but allows phantom reads. |
SERIALIZABLE |
Full transaction isolation (slowest but safest). |
📌 Example: Using Isolation.READ_COMMITTED
to Prevent Dirty Reads
@Transactional(isolation = Isolation.READ_COMMITTED)
public void processTransaction() {
// Transaction code here
}
✅ Ensures only committed data is read by concurrent transactions.
6️⃣ Using @Transactional
with Spring Data JPA Queries
Spring Data JPA methods automatically run inside transactions, but you can override defaults.
📌 Example: Ensuring a Custom Query Runs in a Transaction
@Transactional
@Modifying
@Query("UPDATE User u SET u.email = :email WHERE u.id = :id")
void updateUserEmail(@Param("id") Long id, @Param("email") String email);
✅ @Modifying
queries need @Transactional
to execute updates safely.
🎯 Summary: Best Practices for Using @Transactional
✅ Use @Transactional
at the service layer, not controllers.
✅ Ensure rollback for checked exceptions using rollbackFor = Exception.class
.
✅ Use Propagation.REQUIRES_NEW
for independent transactions.
✅ Set appropriate isolation levels (READ_COMMITTED
, SERIALIZABLE
).
✅ Use @Modifying
with @Transactional
for update and delete queries.
✅ Log exceptions instead of swallowing them inside transactions.
🚀 Following these best practices ensures reliable, consistent database transactions in Spring Boot!
Comments
Post a Comment
Leave Comment