Spring Transactions
@Transactional Annotation
Spring's @Transactional annotation manages database transactions declaratively. When applied to a method or class, Spring wraps the execution in a transaction — committing on success and rolling back on unchecked exceptions (RuntimeException and Error) by default.
Add @EnableTransactionManagement to your configuration class (Spring Boot enables it automatically).
package com.example.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
private final AuditService auditService;
public OrderService(OrderRepository orderRepository,
InventoryService inventoryService,
AuditService auditService) {
this.orderRepository = orderRepository;
this.inventoryService = inventoryService;
this.auditService = auditService;
}
// Default: REQUIRED propagation, DEFAULT isolation, rollback on RuntimeException
@Transactional
public Order placeOrder(Long userId, Long productId, int qty) {
// All operations share the same transaction
inventoryService.deductStock(productId, qty); // participates in this transaction
Order order = orderRepository.save(new Order(userId, productId, qty));
auditService.logOrder(order); // also in same transaction
return order;
// Commits here if no exception
}
// Read-only: hint to DB to optimize (no dirty checking, no flush)
@Transactional(readOnly = true)
public List<Order> getOrdersByUser(Long userId) {
return orderRepository.findByUserId(userId);
}
// Rollback on checked exceptions too
@Transactional(rollbackFor = Exception.class)
public void processPayment(Long orderId) throws Exception {
// Rolls back even if a checked Exception is thrown
}
// Never rollback on IllegalArgumentException
@Transactional(noRollbackFor = IllegalArgumentException.class)
public void updateOrder(Long orderId, String status) {
// IllegalArgumentException won't trigger rollback
}
// SERIALIZABLE isolation: highest level, prevents phantom reads
@Transactional(isolation = Isolation.SERIALIZABLE)
public void criticalOperation() {
// Fully isolated from other transactions
}
// REQUIRES_NEW: always starts a new transaction, suspends the current one
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAuditAlways(String message) {
// This runs in its own transaction — commits even if outer tx rolls back
auditService.save(message);
}
}
Propagation Types
| Propagation | Behavior |
|---|---|
| REQUIRED (default) | Join existing transaction; create new one if none exists |
| REQUIRES_NEW | Always create a new transaction; suspend existing one |
| SUPPORTS | Join existing transaction; run non-transactionally if none |
| NOT_SUPPORTED | Run non-transactionally; suspend existing transaction |
| MANDATORY | Must run within an existing transaction; throw exception if none |
| NEVER | Must NOT run within a transaction; throw exception if one exists |
| NESTED | Run within a nested transaction (savepoint); rollback to savepoint on failure |
Isolation Levels
| Isolation Level | Dirty Read | Non-Repeatable Read | Phantom Read |
|---|---|---|---|
| READ_UNCOMMITTED | Possible | Possible | Possible |
| READ_COMMITTED | Prevented | Possible | Possible |
| REPEATABLE_READ | Prevented | Prevented | Possible |
| SERIALIZABLE | Prevented | Prevented | Prevented |
package com.example.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class ProgrammaticTxService {
private final TransactionTemplate transactionTemplate;
private final OrderRepository orderRepository;
public ProgrammaticTxService(PlatformTransactionManager txManager,
OrderRepository orderRepository) {
// TransactionTemplate wraps PlatformTransactionManager
this.transactionTemplate = new TransactionTemplate(txManager);
this.orderRepository = orderRepository;
}
public Order createOrderProgrammatically(Order order) {
// Configure transaction settings
transactionTemplate.setIsolationLevel(
TransactionTemplate.ISOLATION_READ_COMMITTED);
transactionTemplate.setTimeout(30); // seconds
// execute() returns a value; executeWithoutResult() for void
return transactionTemplate.execute(status -> {
try {
Order saved = orderRepository.save(order);
// ... more operations
return saved;
} catch (Exception e) {
// Manually mark for rollback
status.setRollbackOnly();
throw new RuntimeException("Order creation failed", e);
}
});
}
}
@Service
public class TransactionPitfalls {
// PITFALL 1: Self-invocation bypasses the proxy — @Transactional has no effect
public void outerMethod() {
this.innerMethod(); // Calls directly, NOT through Spring proxy — NO transaction!
}
@Transactional
public void innerMethod() {
// This transaction is IGNORED when called from outerMethod() above
}
// FIX: Inject self or use a separate bean
@Autowired
private TransactionPitfalls self;
public void outerMethodFixed() {
self.innerMethod(); // Goes through proxy — transaction works!
}
// PITFALL 2: @Transactional on private methods — Spring proxy can't intercept
@Transactional
private void privateMethod() {
// NO transaction — Spring AOP only works on public methods
}
// PITFALL 3: Catching RuntimeException prevents rollback
@Transactional
public void catchingException() {
try {
// ... DB operation
throw new RuntimeException("error");
} catch (RuntimeException e) {
// Swallowing the exception — transaction COMMITS instead of rolling back!
System.out.println("Caught: " + e.getMessage());
}
}
// FIX: Re-throw or mark rollback manually
@Transactional
public void catchAndRethrow(TransactionStatus status) {
try {
throw new RuntimeException("error");
} catch (RuntimeException e) {
// Option 1: re-throw
throw e;
// Option 2: mark rollback only (if you need to handle the exception)
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.