Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring read-only transaction: performance hint, not a write guard

Last updated: 5 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

Use readOnly for a read unit of work, but enforce write permissions and invariants elsewhere.

Name the promise accurately

A receipt view service loads a tenant-scoped row and maps it to an immutable response within a read-only transaction. Spring passes the read-only flag to transaction infrastructure; with Hibernate, the chosen flush behavior can skip dirty-check work for a large loaded graph. The flag is not an authorization rule and should not be used as the only defense against a mutation. Proxy invocation still controls whether the annotation is applied.

Keep reads bounded and contained

Return a DTO while the transaction is active rather than exposing a lazy entity to the controller. Pair the read with disabled Open EntityManager in View so a later response serializer cannot silently query the database. A projection may avoid loading an entire entity graph. Inherited Spring Data CRUD reads are commonly configured read-only, while declared query methods and outer service transactions deserve explicit review.

Test what matters

Assert that a denied principal cannot call the write path; do not use a readOnly test as proof of denial. Measure statement count and allocation for a representative read, then inspect the transaction attributes at the service proxy. A database driver or provider may treat read-only differently, so benchmark instead of promising a fixed speedup.

Implementation contract

Java
@Transactional(readOnly = true)
public ReceiptView readReceipt(UUID tenantId, UUID receiptId) {
    ReceiptEntity receipt = receipts.findByTenantIdAndId(
        tenantId, receiptId).orElseThrow(ReceiptNotFound::new);
    return new ReceiptView(receipt.getId(), receipt.getTotalMinor());
}

Cost and verification

A read-only hint can reduce flush and dirty-check overhead with some providers. The dominant cost may still be SQL, hydrated columns, and N+1 reads. A read transaction holds a connection until it exits.

Common Mistakes

  • Do not interpret readOnly as a security boundary or universal database write prohibition.
  • Do not return a lazy entity to be serialized after the transaction closes.
  • Do not assume a self-invoked annotated method entered a transaction proxy.

Read next

Spring transactional methods: call paths and rollback assumptions, Spring Boot Open EntityManager in View: close the response-time query gap, Spring Data JPA projections: return selected fields without a full entity, Spring Data JPA entity graph for a bounded to-one read, Spring JWT tenant claims: reject missing or malformed ownership before conversion.

spring
spring-data-jpa
jpa-readonly-transaction-hint
Storage details