Interview Practice

Chapter 3 of 5

Transactions, JPA queries and concurrent updates

Make consistency and database work visible.
Read aloud is unavailable in this browser. Reading preferences still work.

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.

Read further