Spring Boot: From Basics to Production · Persistence with Spring Data JPA
Transactions with @Transactional
Define transaction boundaries and avoid the common proxy pitfalls.
@Transactional on service methods opens a transaction, commits on success and rolls back on unchecked exceptions (configure rollbackFor for checked ones). Use readOnly = true for queries. It works through proxies, so self-invocation (calling a @Transactional method from the same class) bypasses it, and it only applies to public methods called from outside. Keep transactions short and never call slow external services inside them.
@Service
public class TransferService {
private final AccountRepository accounts;
TransferService(AccountRepository accounts) { this.accounts = accounts; }
@Transactional
public void transfer(long fromId, long toId, long cents) {
Account from = accounts.findById(fromId).orElseThrow();
Account to = accounts.findById(toId).orElseThrow();
from.withdraw(cents); // throws InsufficientFunds (RuntimeException) → rollback
to.deposit(cents);
// no save() needed: managed entities are flushed on commit (dirty checking)
}
@Transactional(readOnly = true)
public AccountView view(long id) {
return accounts.findViewById(id).orElseThrow();
}
}
Common pitfalls
- Self-invocation:
this.transfer()from another method in the same bean skips the proxy. - Checked exceptions don't trigger rollback by default.
- HTTP calls or email sending inside transactions.
Key points
- Service-level transaction boundaries
- Proxy-based: watch self-invocation
- Rollback on unchecked exceptions by default
Further reading: Spring: Declarative transactions
Practice questions
Why doesn't @Transactional work when a method calls another @Transactional method in the same class?
Answer
Transactions are applied by a proxy around the bean; internal calls via this don't go through the proxy, so the annotation on the called method is ignored.
Why don't you need to call save() after modifying a loaded entity?
Answer
Entities loaded in a transaction are managed; Hibernate's dirty checking flushes changes automatically at commit.
Open in the app to mark lessons complete, take notes and track your progress.