Read-only transactions — what does @Transactional(readOnl… — Cracked Java
// Spring Framework & Spring Boot · Transactions Deep Dive
SeniorTheoryTrick

Read-only transactions — what does @Transactional(readOnly = true) actually do?

@Transactional(readOnly = true) is not a no-op and it does not make the database reject writes by itself — it's a hint that flows down to the JDBC driver and the persistence provider to skip work that only matters for writes. The biggest win is on the Hibernate/JPA side.

What it actually does

1. Hibernate flush mode → MANUAL and no dirty checking. This is the real payoff. In a read-only transaction Hibernate sets the flush mode so it won't auto-flush, and — more importantly — it doesn't take a snapshot of loaded entities for dirty checking. Normally Hibernate copies every loaded entity's state so it can detect changes at flush; in read-only mode it skips that, cutting memory and CPU for read-heavy queries.

@Transactional(readOnly = true)
public List<OrderView> recentOrders(long customerId) {
    return orderRepo.findRecent(customerId);   // no dirty-check snapshots, no flush
    // modifying a returned entity here will NOT be persisted
}

2. JDBC Connection.setReadOnly(true). Spring calls this on the connection. It's advisory — the driver/DB may optimize (skip some locking/WAL bookkeeping) but isn't required to. On PostgreSQL it can also matter for routing: a read-only connection can be safely sent to a replica.

3. Routing to read replicas. Combined with an AbstractRoutingDataSource, the read-only flag is the natural signal to route the transaction to a standby/read replica, offloading the primary.

What it does not do

It is not a write guard. Postgres will still execute an UPDATE on a normal connection even if setReadOnly(true) was called unless the server actually enforces it (e.g. the statement hits a hot-standby, which does reject writes). Don't rely on readOnly = true for security; treat it as a performance and routing hint.

Mark your status