skip to content

What does the `isolation` attribute of `@Transactional` control, and what are the five values Spring offers?

level: juniorimportance: must knowfreq 62%

answer

  1. I in ACID — the visibility knob
  2. Isolation enum: DEFAULT + 4 real levels
  3. Maps to JDBC Connection.setTransactionIsolation
  4. Higher = more consistent, less concurrent
  5. DEFAULT = let the DB decide

basics

~10 s

It sets how much one transaction is shielded from data other in-flight transactions are changing. Spring's Isolation enum offers DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, and SERIALIZABLE — higher levels give more consistency but less concurrency.

solid answer

~40 s

`isolation` controls how visible concurrent transactions' uncommitted or committed changes are to the current transaction — the classic trade-off between consistency and concurrency. Spring's `org.springframework.transaction.annotation.Isolation` enum has five values: `DEFAULT` (use the database's own default), `READ_UNCOMMITTED`, `READ_COMMITTED`, `REPEATABLE_READ`, and `SERIALIZABLE`. Spring maps these onto the JDBC `Connection` isolation constants; the transaction manager applies the level to the physical connection when the transaction starts. Higher levels progressively prevent read anomalies (dirty, non-repeatable, phantom reads) but cost throughput through more locking or aborts. `DEFAULT` is by far the most common in real apps — you let the DB decide (often READ_COMMITTED on Postgres/Oracle, REPEATABLE_READ on MySQL InnoDB) and only override when a specific consistency requirement demands it.

code

java · 16 lines
java
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountService {

    // Let the database pick its own default level (the common case).
    @Transactional
    public Account load(long id) { /* ... */ }

    // Override only where a stronger guarantee is required.
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void transfer(long from, long to, BigDecimal amount) {
        // ...
    }
}

go deeper

for a junior

Must name the five enum values and state the consistency-vs-concurrency trade-off.

for a middle

Should know it maps to JDBC connection isolation and that DEFAULT defers to the DB.

for a senior

Explains when Spring physically applies and resets the level, and why DEFAULT is the pragmatic norm.

for a principal

Frames isolation as a per-operation tuning decision balanced against lock contention and DB engine behavior.

## What isolation means When many transactions run at once, one transaction can accidentally see intermediate or changing data produced by another. **Isolation level** is the knob that decides how much a transaction is protected from those concurrent effects. It is one of the four ACID properties (the *I*). ## The Spring attribute You set it declaratively: ```java @Transactional(isolation = Isolation.REPEATABLE_READ) ``` The enum is `org.springframework.transaction.annotation.Isolation` with five constants: | Spring value | JDBC constant | Meaning | |---|---|---| | `DEFAULT` | (-1) | Don't override — use the datastore's own default level | | `READ_UNCOMMITTED` | `TRANSACTION_READ_UNCOMMITTED` (1) | Weakest; can see uncommitted data | | `READ_COMMITTED` | `TRANSACTION_READ_COMMITTED` (2) | Only see committed data | | `REPEATABLE_READ` | `TRANSACTION_REPEATABLE_READ` (4) | Re-reading a row gives the same value | | `SERIALIZABLE` | `TRANSACTION_SERIALIZABLE` (8) | Strongest; transactions behave as if run one-at-a-time | ## How Spring applies it When the transaction manager (`DataSourceTransactionManager`, `JpaTransactionManager`, etc.) begins a **new physical transaction**, it takes the JDBC `Connection` and calls `connection.setTransactionIsolation(level)` (unless the value is `DEFAULT`). After the transaction ends, Spring resets the connection to its prior level before returning it to the pool. Because the level lives on the connection, it is set **once when the transaction/connection starts** — you cannot meaningfully change it partway through. ## The consistency vs. concurrency trade-off Going up the levels prevents more anomalies but reduces parallelism: databases achieve higher levels with more/longer locks or with snapshot-and-abort schemes, so you get more blocking, more serialization failures, or lower throughput. That is why `DEFAULT` (letting the DB pick, usually READ_COMMITTED-ish) is the pragmatic norm, and you raise it only for the specific method that needs stronger guarantees. ## Gotchas - `DEFAULT` does **not** mean READ_COMMITTED everywhere — MySQL InnoDB defaults to REPEATABLE_READ, Oracle/Postgres to READ_COMMITTED. - Not every database supports every level (Oracle has no READ_UNCOMMITTED or REPEATABLE_READ). - The level only takes effect where a real transaction/connection is created — it is ignored when a method merely *joins* an already-running transaction (see propagation).

  • What does `Isolation.DEFAULT` actually resolve to?
    It tells Spring not to override anything, so the effective level is whatever the underlying database defines as its default — e.g. READ_COMMITTED on PostgreSQL/Oracle, REPEATABLE_READ on MySQL InnoDB. It is database-specific, not a fixed level.
  • Where in the lifecycle is the isolation level applied?
    When the transaction manager begins a new physical transaction it calls `Connection.setTransactionIsolation(...)` on the JDBC connection, and restores the original level when the transaction completes and the connection returns to the pool.

saying these in an interview costs you the question

  • Saying DEFAULT always equals READ_COMMITTED
  • Thinking isolation controls whether a method runs in a transaction at all (that's propagation)
  • Believing you can change the level in the middle of a transaction

context