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.
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:10The 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
