skip to content

readOnly Semantics

readOnly=true is a hint: it sets Hibernate's flush mode to manual and can steer routing to a replica, but it does not forbid writes. Correcting exactly that misconception is why the question gets asked.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What does @Transactional(readOnly = true) do in Spring?

level: juniorimportance: must knowfreq 70%

answer

  1. Hint, not a lock
  2. FlushMode.MANUAL
  3. Connection.setReadOnly(true)
  4. Skips dirty-check + flush
  5. Optimization + replica routing

basics

~20 s

It marks a transaction as read-only: a hint that the method only reads data. Spring passes this to Hibernate and the JDBC driver so they can optimize, for example by skipping dirty-checking and the automatic flush.

solid answer

~40 s

@Transactional(readOnly = true) declares that a transaction won't modify data. It is a hint, not a lock. Spring propagates the flag two ways: it may call Connection.setReadOnly(true) on the JDBC connection (drivers can optimize or route to a replica), and for Hibernate/JPA it sets the Session FlushMode to MANUAL, so Hibernate skips automatic dirty-checking and the flush before queries and at commit. The payoffs are less CPU spent snapshotting and comparing entities, plus a clear signal for read/write replica routing. Crucially it does NOT forbid writes at the JPA level: an explicit flush or native SQL can still persist changes. Use it on service methods that only query, especially those loading large object graphs.

code

java · 16 lines
java
@Service
public class ReportService {

    private final OrderRepository orders;

    public ReportService(OrderRepository orders) {
        this.orders = orders;
    }

    @Transactional(readOnly = true)
    public List<OrderView> monthlyReport(int year, int month) {
        // Only queries run here; Hibernate uses FlushMode.MANUAL,
        // so no dirty-checking flush occurs at commit.
        return orders.findByPeriod(year, month);
    }
}

go deeper

for a junior

Know it is a hint for read-only work that lets Hibernate/JDBC optimize.

for a middle

Know the two mechanisms: Connection.setReadOnly and Hibernate FlushMode.MANUAL.

for a senior

Can explain dirty-checking avoidance and that it is not enforcement.

for a principal

Frames it within replica routing, connection pools, and where real enforcement must live.

## What it is `@Transactional` is Spring's declarative transaction annotation. Its `readOnly` attribute (default `false`) declares that the annotated method performs only reads. `@Transactional(readOnly = true)` is a *hint* to the transaction infrastructure that no data will be modified. ## What actually happens under the hood When Spring's `PlatformTransactionManager` starts the transaction it reads `TransactionDefinition.isReadOnly()` and does two independent things: 1. **JDBC level** — the transaction manager may call `java.sql.Connection.setReadOnly(true)`. This is itself only a hint to the driver; some drivers optimize, some route the connection to a read replica, many ignore it. It usually does not throw on writes. 2. **Hibernate/JPA level** — when using `JpaTransactionManager` with Hibernate, Spring sets the Hibernate `Session`'s flush mode to `FlushMode.MANUAL`. That means Hibernate will not auto-flush pending changes before running queries, and will not flush at commit. Because no flush happens, entity modifications are never written (and Hibernate can also skip taking the dirty-checking snapshot, saving memory and CPU). ## Why it is an optimization In a normal read-write session Hibernate keeps a *loaded snapshot* of every managed entity and, at flush time, compares each field to detect changes (dirty checking) so it can emit UPDATE statements. In a read-only transaction there is nothing to write, so this bookkeeping is pure overhead. `readOnly = true` eliminates the flush and lets Hibernate avoid that work. ## What it is NOT It is not enforcement. It does not make the method reject writes at the ORM layer. It does not put the database into a read-only mode by itself. It is advisory. ## When to use it Put `readOnly = true` on service methods (or query-only repository facades) that only fetch data — reports, list endpoints, lookups. A common pattern is `@Transactional(readOnly = true)` on the class and override with `@Transactional` on the few write methods.

  • Is readOnly=true enforced — will Spring stop you from saving an entity?
    No. At the JPA level it just sets FlushMode.MANUAL so auto-flush is skipped; it doesn't reject writes. An explicit session.flush(), a native/JPQL bulk update, or JDBC still writes.
  • Name one concrete performance benefit.
    Hibernate skips the automatic flush and the dirty-checking snapshot/compare of managed entities, cutting CPU and memory for read-heavy transactions.

saying these in an interview costs you the question

  • Saying readOnly=true throws an exception if you try to write
  • Claiming it locks the rows or the table
  • Thinking it is a database-level read-only mode Spring enforces

context

open as a page

Does @Transactional(readOnly = true) prevent writes? What happens if you modify an entity inside one?

level: middleimportance: must knowfreq 65%

basics

~20 s

Usually nothing is written, but not because writes are forbidden. Because FlushMode is MANUAL, Hibernate never auto-flushes, so a dirty entity change is silently discarded at commit. It is not an error — it is a missing flush.

open as a page

What does readOnly=true mean at the JDBC/connection level, and how is it used for replica routing?

level: seniorimportance: should knowfreq 40%

basics

~20 s

At the JDBC level Spring may call Connection.setReadOnly(true). That is a driver hint some databases optimize and, more usefully, apps use it to route the connection to a read replica while write transactions go to the primary.

open as a page

How does readOnly=true interact with Hibernate FlushMode and dirty checking?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Spring's Hibernate integration sets the Session FlushMode to MANUAL when readOnly=true. That disables the automatic flush before queries and at commit, so Hibernate skips dirty-checking of managed entities, saving the snapshot-and-compare work.

open as a page

What are the propagation and enforcement pitfalls of @Transactional(readOnly = true)? When does the flag actually take effect?

level: principalimportance: should knowfreq 30%

basics

~20 s

readOnly is set when a physical transaction begins. If an inner method joins an existing (REQUIRED) transaction, its own readOnly value is ignored — the outer transaction's flag wins. And because it is only a hint, real no-write guarantees must come from the DB, not this flag.

open as a page