Working with Transactions
1. Understanding Transaction Management (@Transactional)
Example: Transactional money transfer
@Service
public class TransferService {
@Transactional
public void transfer(Long from, Long to, BigDecimal amount) {
accounts.debit(from, amount);
accounts.credit(to, amount);
} // commit on return; rollback on RuntimeException
}
| Attribute | Default |
|---|---|
propagation |
REQUIRED |
isolation |
DEFAULT (DB) |
readOnly |
false |
timeout |
-1 (none) |
rollbackFor |
RuntimeException + Error |
2. Configuring Transaction Propagation
| Propagation | Behavior |
|---|---|
REQUIRED |
Join existing or start new |
REQUIRES_NEW |
Suspend current, start new |
SUPPORTS |
Join if exists, else non-tx |
NOT_SUPPORTED |
Suspend if active |
MANDATORY |
Throws if no active tx |
NEVER |
Throws if tx active |
NESTED |
Savepoint within outer tx |
3. Setting Transaction Isolation Levels
| Level | Prevents |
|---|---|
READ_UNCOMMITTED |
Nothing |
READ_COMMITTED |
Dirty reads |
REPEATABLE_READ |
+ Non-repeatable reads |
SERIALIZABLE |
+ Phantom reads (slowest) |
4. Configuring Transaction Timeout
Example: Transaction with timeout
@Transactional(timeout = 5) // seconds
public Report generate() { /* must finish in 5s */ }
5. Handling Read-Only Transactions
| Effect | Description |
|---|---|
| Hibernate flush mode | Set to MANUAL (no dirty checking) |
| Connection | Hint to use read replica (with custom routing) |
| Performance | Skips snapshot creation, reduces overhead |
6. Rolling Back Transactions
Example: Rollback rules and programmatic rollback
@Transactional(rollbackFor = Exception.class,
noRollbackFor = NotFoundException.class)
public void process(Order o) throws BusinessException { /* … */ }
// Programmatic
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
| Default | Detail |
|---|---|
| Rolls back on | Unchecked exceptions + Error |
| Commits on | Checked exceptions (unless rollbackFor) |
7. Using Programmatic Transactions (TransactionTemplate)
Example: Programmatic REQUIRES_NEW transaction
@Service
public class BatchService {
private final TransactionTemplate tx;
public BatchService(PlatformTransactionManager tm) {
this.tx = new TransactionTemplate(tm);
tx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
public void run() {
tx.execute(status -> { /* work */ return null; });
}
}
8. Understanding Transaction Boundaries
| Pitfall | Reason |
|---|---|
| Self-invocation | Calling this.method() bypasses proxy → no tx |
| Private methods | Not proxied → no tx |
| Final classes/methods | CGLIB cannot proxy |
| Long tx | Holds DB connection — limit duration |
Warning: Apply
@Transactional at the service entry, not on
repository methods.9. Handling Distributed Transactions (JTA)
| Element | Notes |
|---|---|
| JTA coordinator | Atomikos, Narayana |
| Use case | Multiple XA resources (DB + JMS) |
| Modern alt | Saga / outbox pattern instead of XA |
10. Using @Lock for Pessimistic Locking
Example: Pessimistic write lock query
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select a from Account a where a.id = :id")
Optional<Account> findForUpdate(@Param("id") Long id);
| LockMode | SQL |
|---|---|
PESSIMISTIC_READ |
SELECT … FOR SHARE |
PESSIMISTIC_WRITE |
SELECT … FOR UPDATE |
OPTIMISTIC |
Version check on flush |
OPTIMISTIC_FORCE_INCREMENT |
Bump version even if unchanged |
11. Using @Version for Optimistic Locking
Example: Optimistic lock with @Version
@Entity
public class Product {
@Id Long id;
@Version Long version; // auto-incremented on update
}
Note: Update fails with
OptimisticLockException if version
differs.12. Optimizing Transaction Performance
| Tip | Reason |
|---|---|
Mark queries readOnly |
Skip dirty checking |
| Limit transaction scope | Hold connections briefly |
| Use batch inserts | spring.jpa.properties.hibernate.jdbc.batch_size=50 |
Avoid open-in-view |
Prevents view layer DB access |
| Connection pool sizing | 2 × cores typically optimal |