05 - Spring Transactions Notes
A beginner-to-advanced guide to Spring transaction management,
@Transactional, propagation, isolation, rollback rules, and the transaction manager for Spring Professional Certification candidates. Covers Spring Framework 6 and Spring Boot 3 concepts.
Table of Contents
@Transactional- Propagation
- Isolation
- Rollback Rules
- Transaction Manager
- Nested Transactions
- Read Only Transactions
- Common Pitfalls
- Interview Questions
- Cheat Sheet
1. @Transactional
@Transactional declares that a method or class should run inside a Spring-managed transaction.
@Service
public class PaymentService {
@Transactional
public void pay(Long orderId) {
// database operations are executed in one transaction
}
}
When placed on a class, it applies to all public methods unless overridden at method level.
@Service
@Transactional(readOnly = true)
public class OrderQueryService {
public Order findById(Long id) {
return repository.findById(id).orElseThrow();
}
@Transactional(readOnly = false)
public Order create(Order order) {
return repository.save(order);
}
}
Important Rules
| Rule | Explanation |
|---|---|
| Works through Spring proxies | Calls must go through the Spring proxy for transaction advice to apply. |
| Public methods are the usual target | With proxy-based AOP, public methods are the common and recommended use. |
| Self-invocation does not trigger transactions | A method inside the same class calling another @Transactional method bypasses the proxy. |
| Runtime exceptions roll back by default | Checked exceptions do not roll back unless configured. |
| Keep transactions short | Avoid slow network calls or long computations inside transactions. |
Common Attributes
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
readOnly = false,
timeout = 30,
rollbackFor = Exception.class
)
| Attribute | Purpose |
|---|---|
propagation | Defines how the method participates in an existing transaction. |
isolation | Defines how visible concurrent changes are. |
readOnly | Optimizes the transaction for read operations. |
timeout | Maximum time before the transaction times out. |
rollbackFor | Exceptions that should trigger rollback. |
noRollbackFor | Exceptions that should not trigger rollback. |
2. Propagation
Propagation controls what happens when a transactional method is called while another transaction may already exist.
REQUIRED
Uses the existing transaction if one exists; otherwise creates a new one.
@Transactional(propagation = Propagation.REQUIRED)
public void placeOrder() {
}
This is the default and most commonly used propagation type.
REQUIRES_NEW
Always starts a new transaction. If another transaction exists, it is suspended.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAuditLog() {
}
Useful when an operation must commit independently, such as audit logging.
SUPPORTS
Uses an existing transaction if one exists; otherwise runs without a transaction.
@Transactional(propagation = Propagation.SUPPORTS)
public List<Order> findOrders() {
return repository.findAll();
}
Useful for read operations that can work with or without a transaction.
MANDATORY
Requires an existing transaction. Throws an exception if none exists.
@Transactional(propagation = Propagation.MANDATORY)
public void updateInventory() {
}
Useful when a method must only be called as part of a larger transactional workflow.
NOT_SUPPORTED
Suspends an existing transaction and runs without one.
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void generateReport() {
}
Useful for long-running non-transactional work.
NEVER
Runs only if no transaction exists. Throws an exception if a transaction is active.
@Transactional(propagation = Propagation.NEVER)
public void nonTransactionalOperation() {
}
NESTED
Runs inside a nested transaction using a savepoint if a transaction exists. If no transaction exists, behaves like REQUIRED.
@Transactional(propagation = Propagation.NESTED)
public void applyDiscount() {
}
Useful when part of a larger transaction can roll back independently to a savepoint.
Propagation Summary
| Propagation | Existing Transaction | No Existing Transaction |
|---|---|---|
REQUIRED | Join existing transaction | Create new transaction |
REQUIRES_NEW | Suspend existing and create new | Create new transaction |
SUPPORTS | Join existing transaction | Run non-transactionally |
MANDATORY | Join existing transaction | Throw exception |
NOT_SUPPORTED | Suspend existing transaction | Run non-transactionally |
NEVER | Throw exception | Run non-transactionally |
NESTED | Create savepoint inside existing transaction | Create new transaction |
3. Isolation
Isolation controls how one transaction is affected by concurrent transactions.
Concurrency Problems
| Problem | Meaning |
|---|---|
| Dirty read | Reading uncommitted data from another transaction. |
| Non-repeatable read | Reading the same row twice and seeing different committed values. |
| Phantom read | Re-running a query and seeing new or removed rows due to another committed transaction. |
| Lost update | Two transactions update the same data and one update overwrites the other. |
Isolation Levels
| Isolation Level | Dirty Read | Non-Repeatable Read | Phantom Read | Notes |
|---|---|---|---|---|
READ_UNCOMMITTED | Possible | Possible | Possible | Lowest isolation; rarely used. |
READ_COMMITTED | Prevented | Possible | Possible | Common default in many databases. |
REPEATABLE_READ | Prevented | Prevented | Possible | Common default in MySQL InnoDB. |
SERIALIZABLE | Prevented | Prevented | Prevented | Highest isolation; safest but slowest. |
Spring Isolation Values
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateAccount() {
}
| Value | Description |
|---|---|
DEFAULT | Uses the default isolation level of the database. |
READ_UNCOMMITTED | Allows dirty reads. |
READ_COMMITTED | Prevents dirty reads. |
REPEATABLE_READ | Prevents dirty and non-repeatable reads. |
SERIALIZABLE | Fully serializes transactions. |
4. Rollback Rules
By default, Spring rolls back transactions for unchecked exceptions.
| Exception Type | Default Behavior |
|---|---|
RuntimeException | Rollback |
Error | Rollback |
| Checked exception | Commit unless configured |
Rollback for Checked Exceptions
@Transactional(rollbackFor = Exception.class)
public void importData() throws Exception {
throw new Exception("Import failed");
}
Prevent Rollback for Specific Exceptions
@Transactional(noRollbackFor = BusinessException.class)
public void process() {
throw new BusinessException("Business validation failed");
}
Programmatic Rollback
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
Use programmatic rollback sparingly. Prefer exception-based rollback rules because they are clearer and easier to test.
Rollback-Only Marker
If an inner transactional operation marks a transaction as rollback-only, the outer transaction cannot commit successfully. Spring may throw UnexpectedRollbackException when the outer method tries to commit.
@Transactional
public void outer() {
inner();
}
@Transactional
public void inner() {
throw new RuntimeException("Failure");
}
If inner() participates in the same transaction and the exception causes rollback, the whole transaction is affected.
5. Transaction Manager
A transaction manager coordinates transaction boundaries: begin, commit, rollback, suspend, and resume.
Spring uses the PlatformTransactionManager abstraction.
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition);
void commit(TransactionStatus status);
void rollback(TransactionStatus status);
}
Common Transaction Managers
| Transaction Manager | Used For |
|---|---|
DataSourceTransactionManager | Plain JDBC and MyBatis with a single DataSource. |
JpaTransactionManager | JPA/Hibernate. |
JtaTransactionManager | Distributed transactions across multiple resources. |
R2dbcTransactionManager | Reactive transactions with R2DBC. |
Spring Boot Auto-Configuration
Spring Boot automatically configures a transaction manager when it finds transaction-capable infrastructure.
Examples:
| Dependency/Bean | Auto-Configured Manager |
|---|---|
JDBC DataSource | DataSourceTransactionManager |
JPA EntityManagerFactory | JpaTransactionManager |
R2DBC ConnectionFactory | R2dbcTransactionManager |
If multiple transaction managers exist, specify which one to use.
@Transactional(transactionManager = "ordersTransactionManager")
public void updateOrder() {
}
Declarative vs Programmatic Transactions
Declarative transaction:
@Transactional
public void createUser() {
}
Programmatic transaction:
transactionTemplate.execute(status -> {
repository.save(user);
return null;
});
Prefer declarative transactions for most business services. Use TransactionTemplate when transaction boundaries must be controlled inside a method.
6. Nested Transactions
Nested transactions use savepoints inside an existing transaction.
@Transactional
public void checkout() {
reserveInventory();
applyCoupon();
chargePayment();
}
@Transactional(propagation = Propagation.NESTED)
public void applyCoupon() {
}
If applyCoupon() fails, Spring can roll back to the savepoint without necessarily rolling back the entire outer transaction.
NESTED vs REQUIRES_NEW
| Feature | NESTED | REQUIRES_NEW |
|---|---|---|
| Transaction type | Savepoint inside existing transaction | Completely separate transaction |
| Outer transaction | Continues after savepoint rollback | Suspended while inner transaction runs |
| Commit behavior | Final commit depends on outer transaction | Inner transaction commits independently |
| Resource usage | Usually same database connection | Usually needs another connection |
| Support | Requires savepoint support | Widely supported |
Limitations
| Limitation | Explanation |
|---|---|
| Requires savepoint support | The underlying transaction manager and database must support savepoints. |
| Not the same as independent commit | A nested transaction does not commit independently of the outer transaction. |
| Proxy rules still apply | Self-invocation can prevent NESTED from being applied. |
7. Read Only Transactions
readOnly = true tells Spring and the persistence provider that the transaction is intended only for reads.
@Transactional(readOnly = true)
public List<Product> findProducts() {
return productRepository.findAll();
}
Benefits
| Benefit | Explanation |
|---|---|
| Optimization hint | The database or ORM may optimize for reads. |
| Hibernate optimization | Hibernate may skip dirty checking or adjust flush behavior. |
| Clear intent | Shows that the method should not modify data. |
Important Notes
| Note | Explanation |
|---|---|
| Not always enforced | Some databases treat read-only as a hint. |
| Writes may still happen in some cases | Do not rely only on readOnly = true for security or validation. |
| Best for query services | Use it on read-only service methods or classes. |
@Service
@Transactional(readOnly = true)
public class ProductQueryService {
public ProductDetails getDetails(Long id) {
return productRepository.findDetails(id);
}
}
8. Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
@Transactional not working on private method | Proxy cannot intercept private method | Put it on a public service method |
| Self-invocation bypasses transaction | Method call does not go through proxy | Move method to another bean or call through proxy |
| Checked exception does not roll back | Spring rolls back unchecked exceptions by default | Add rollbackFor |
| Long transaction causes locks | Transaction holds database resources too long | Keep transaction boundaries small |
LazyInitializationException | Entity lazy field accessed outside transaction/session | Fetch required data inside transaction or use DTO queries |
| Unexpected rollback | Inner operation marks shared transaction rollback-only | Use proper propagation or exception handling |
9. Interview Questions
1. What does @Transactional do?
It defines a transactional boundary around a method or class. Spring opens a transaction before method execution and commits it if the method completes successfully. If a rollback-triggering exception occurs, Spring rolls back the transaction.
2. Why does @Transactional sometimes not work?
Common reasons include self-invocation, using it on private methods, calling a method on an object not managed by Spring, missing transaction manager configuration, or catching exceptions without rethrowing them.
3. What is the default propagation level?
The default propagation is REQUIRED. It joins an existing transaction or creates a new one if none exists.
4. What is the difference between REQUIRED and REQUIRES_NEW?
REQUIRED participates in the current transaction. REQUIRES_NEW suspends the current transaction and starts a separate new transaction that can commit or roll back independently.
5. What is the difference between NESTED and REQUIRES_NEW?
NESTED uses a savepoint within the existing transaction. REQUIRES_NEW creates a completely separate transaction. A nested transaction depends on the outer transaction's final commit, while a REQUIRES_NEW transaction commits independently.
6. Which exceptions cause rollback by default?
Spring rolls back by default for RuntimeException and Error. Checked exceptions do not cause rollback unless configured with rollbackFor.
7. How do you roll back for a checked exception?
@Transactional(rollbackFor = IOException.class)
public void upload() throws IOException {
}
8. What is transaction isolation?
Isolation defines how changes made by one transaction are visible to other concurrent transactions. Higher isolation improves consistency but can reduce concurrency.
9. What is a dirty read?
A dirty read happens when one transaction reads uncommitted data from another transaction. If the other transaction rolls back, the first transaction has read invalid data.
10. What is a phantom read?
A phantom read happens when a transaction runs the same query multiple times and sees a different set of rows because another transaction inserted or deleted matching rows.
11. What does readOnly = true do?
It marks the transaction as read-only. It can help the database or ORM optimize execution and communicates that the method should not perform writes.
12. Can @Transactional be used on interfaces?
It can be used on interfaces, but placing it on concrete service classes or methods is usually clearer and more reliable, especially when proxy and inheritance behavior matters.
13. What is UnexpectedRollbackException?
It occurs when a transaction is marked rollback-only but the outer code still attempts to commit. This often happens when an inner transactional operation fails inside a shared transaction.
14. What is the role of PlatformTransactionManager?
It abstracts transaction operations such as beginning, committing, rolling back, suspending, and resuming transactions.
15. When would you use TransactionTemplate?
Use TransactionTemplate when transaction boundaries need to be controlled programmatically inside a method or when different parts of the same method need different transactional behavior.
10. Cheat Sheet
@Transactional Defaults
| Setting | Default |
|---|---|
| Propagation | REQUIRED |
| Isolation | DEFAULT |
| Rollback | RuntimeException and Error |
| Read-only | false |
| Timeout | Database or transaction manager default |
Propagation Quick Reference
| Need | Use |
|---|---|
| Join current transaction or create one | REQUIRED |
| Always create independent transaction | REQUIRES_NEW |
| Run with transaction only if one exists | SUPPORTS |
| Fail if no transaction exists | MANDATORY |
| Always run without transaction | NOT_SUPPORTED |
| Fail if a transaction exists | NEVER |
| Roll back part of a transaction to savepoint | NESTED |
Isolation Quick Reference
| Need | Use |
|---|---|
| Use database default | DEFAULT |
| Maximum performance, weak consistency | READ_UNCOMMITTED |
| Prevent dirty reads | READ_COMMITTED |
| Prevent dirty and non-repeatable reads | REPEATABLE_READ |
| Strongest consistency | SERIALIZABLE |
Rollback Quick Reference
@Transactional
Rolls back on unchecked exceptions.
@Transactional(rollbackFor = Exception.class)
Rolls back on checked and unchecked exceptions.
@Transactional(noRollbackFor = BusinessException.class)
Does not roll back for BusinessException.
Best Practices
| Practice | Reason |
|---|---|
| Put transactions on service methods | Keeps business operations atomic. |
| Keep transactions short | Reduces locks and contention. |
| Avoid external API calls inside transactions | Prevents long-held database resources. |
Use readOnly = true for queries | Improves clarity and may improve performance. |
| Be explicit for checked exceptions | Default rollback does not include them. |
| Choose propagation intentionally | Prevents accidental shared rollback behavior. |
| Use DTO queries for read-heavy screens | Avoids lazy loading issues and unnecessary entity tracking. |