Transactions are boundaries with interception
A transaction groups storage operations with commit/rollback semantics supplied by the underlying system. In typical Spring proxy mode, advice applies when a call passes through the bean proxy. A method invoking another annotated method on itself bypasses that interception. A practical remedy is to place the transactional use case in an appropriately separate bean. Default rollback normally covers RuntimeException and Error; checked exceptions need suitable rules unless application defaults change.
Propagation and isolation solve different problems
REQUIRED joins or creates a transaction; REQUIRES_NEW suspends an outer transaction and commits independently, often needing another connection. It can exhaust a pool when every outer transaction holds a connection while waiting for a new one. Isolation governs what competing transactions can observe. Choose storage constraints and locking deliberately; annotations cannot make an external payment and a local database commit one atomic action.
JPA convenience can hide expensive I/O
Loading roots and then accessing lazy associations may issue an N+1 query pattern. Inspect SQL counts and result cardinality; consider joins, entity graphs, batching or DTO queries. Fetching everything eagerly is not a universal solution and collection joins can complicate pagination. Optimistic version checks detect stale updates; pessimistic locks block competitors. Retry only with a clear idempotency and side-effect policy.
Worked Java example
Illustrative snippet: imports, surrounding types or collaborators may be omitted. Read the assumptions before adapting it.
@Service
class TransferService {
@Transactional
public void transfer(long from, long to, BigDecimal amount) {
debit(from, amount);
credit(to, amount);
}
// debit/credit implementations and invariants omitted.
}Walkthrough
The transaction wraps the public use case when invoked through its managed proxy. debit and credit must use the intended transactional storage resources; an uncaught configured rollback-triggering failure should undo storage changes. Calling this method through this from the same object does not create a new proxy-intercepted boundary. Lock ordering, sufficient funds and numeric/currency validation are additional responsibilities.
Practice it yourself
Design an integration test that proves a credit failure rolls back a successful debit, without hiding the result in the test framework transaction.
Check your reasoning
Invoke the managed service through its proxy, induce the failure and read balances in a separate committed observation boundary. Assert neither balance changed. A mocked repository cannot prove rollback; avoid an outer test transaction that makes the assertion accidentally pass.
Recall checklist
- Explain the main invariant or contract without looking.
- Trace the example and test an edge case.
- State time, space, input variables and assumptions where relevant.
- Explain one alternative and when you would choose it.