When to Use @Transactional(readOnly=true) in Spring: Benefits, Pitfalls, and Performance Insights

This article explains how Spring's @Transactional(readOnly=true) works, why it can boost performance and reduce memory usage, and examines the trade‑offs of applying it to service‑layer read‑only methods versus repository methods, backed by code examples and connection‑usage tests.

Java Backend Technology
Java Backend Technology
Java Backend Technology
When to Use @Transactional(readOnly=true) in Spring: Benefits, Pitfalls, and Performance Insights

Today we discuss Spring's @Transactional(readOnly = true) annotation.

1. How @Transactional(readOnly = true) Works and Why It Improves Performance

The readOnly flag is a hint to the transaction manager that the transaction is effectively read‑only, allowing runtime optimizations. In JpaTransactionManager the doBegin method delegates to JpaDialect, which eventually calls HibernateJpaDialect.beginTransaction. During transaction start, prepareFlushMode sets the Hibernate session flush mode based on the read‑only flag. If readOnly = true, the session is set to FlushMode.MANUAL and session.setDefaultReadOnly(true) is invoked, disabling dirty checking and snapshot maintenance for entities.

Consequences in Hibernate:

/**
 * A boolean flag that can be set to {@code true} if the transaction is
 * effectively read‑only, allowing for corresponding optimizations at runtime.
 * Defaults to {@code false}.
 */
boolean readOnly() default false;

Key benefits:

Performance improvement: read‑only entities are not dirty‑checked.

Memory saving: no snapshot of persistent state is kept.

Data consistency: changes to read‑only entities are not persisted.

When using primary‑replica or read‑only database clusters, the annotation enables connections to read‑only replicas.

2. Should You Always Add @Transactional(readOnly = true) to Service‑Layer Read‑Only Methods?

While @Transactional(readOnly = true) offers many advantages, using it indiscriminately can cause issues such as database deadlocks, reduced throughput, and connection starvation because each transaction holds a DB connection until the method finishes.

We performed a test comparing two scenarios:

Service‑layer method annotated with @Transactional(readOnly = true).

Repository‑layer method (default @Transactional(readOnly = true) in SimpleJpaRepository) annotated similarly.

Test code:

@Transactional(readOnly = true)
public List<UserDto> transactionalReadOnlyOnService() {
    List<UserDto> userDtos = userRepository.findAll().stream()
        .map(userMapper::toDto)
        .toList();
    timeSleepAndPrintConnection();
    return userDtos;
}

public List<UserDto> transactionalReadOnlyOnRepository() {
    List<UserDto> userDtos = userRepository.findAll().stream()
        .map(userMapper::toDto)
        .toList();
    timeSleepAndPrintConnection();
    return userDtos;
}

Results:

Service layer:
activeConnections:0, IdleConnections:10, TotalConnections:10
start transactionalReadOnlyOnService!!
Hibernate: select ... from users u1_0
activeConnections:1, IdleConnections:9, TotalConnections:10
... (connection stays until method end)
end transactionalReadOnlyOnService!!
activeConnections:0, IdleConnections:10, TotalConnections:10
Repository layer:
activeConnections:0, IdleConnections:10, TotalConnections:10
start transactionalReadOnlyOnRepository!!
Hibernate: select ... from users u1_0
activeConnections:0, IdleConnections:10, TotalConnections:10
end transactionalReadOnlyOnRepository!!
activeConnections:0, IdleConnections:10, TotalConnections:10

The repository‑layer transaction releases the connection immediately after the query returns, whereas the service‑layer transaction holds the connection until the method completes. Therefore, long‑running logic in a service‑layer read‑only method can exhaust database connections.

3. Review

In summary, @Transactional(readOnly = true) provides:

Performance gains by skipping dirty checks.

Memory savings by not keeping entity snapshots.

Data consistency because modifications are not persisted.

Ability to route reads to read‑only replicas.

However, applying it to service‑layer methods that contain heavy processing may lead to deadlocks, performance degradation, and connection shortages. Use it for pure read‑only queries, but consider moving extensive logic out of the transactional scope.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

springtransactionalreadonlyHibernatejpa
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.