@transactional(readonly = true) 并非自动加速查询的开关,其性能提升需同时满足:使用支持只读事务的管理器、连接池启用只读配置、jdbc驱动与数据库正确响应setreadonly(true)、且未手动创建session/connection。

@Transactional(readOnly = true) 不是自动加速查询的“开关”,而是一个事务管理提示,它的性能价值取决于底层事务管理器、数据库驱动和实际执行路径是否真正响应这个提示。
readOnly = true 起作用的前提条件
只有当以下几点全部满足时,readOnly = true 才可能带来可观测的性能提升:
- 使用的是支持只读事务语义的事务管理器(如
JpaTransactionManager或DataSourceTransactionManager配合兼容 JDBC 驱动) - 数据库连接池(如 HikariCP)配置了
connectionInitSql或启用只读连接标记(例如 MySQL 的setReadOnly(true)) - 底层 JDBC 驱动确实将
Connection.setReadOnly(true)传递给数据库,并被数据库引擎识别(如 PostgreSQL、MySQL 8.0+、Oracle) - 没有在方法内手动创建 Session / EntityManager / Connection(否则绕过 Spring 管理,
readOnly失效)
常见误用:手动 openSession() 导致 readOnly 失效
下面写法看似只读,实则完全绕过 Spring 事务上下文:
@Transactional(readOnly = true)
public Order findByOrderId(String id) {
Session session = sessionFactory.openSession(); // ❌ 错误:脱离 Spring 管理
List<order> list = session.createQuery(...).list();
session.close(); // ❌ 手动关闭,连接未归还池或未复用
return list.isEmpty() ? null : list.get(0);
}</order>
正确做法是依赖 Spring 注入的 EntityManager 或 JdbcTemplate,让框架自动绑定到当前事务上下文:
- 用
@PersistenceContext获取受管 EntityManager - 用
JdbcTemplate查询(它会自动复用当前事务连接) - 确保 DAO 层不调用
openSession()、createEntityManager()等原始 API
配合数据库层真正生效的关键配置
仅加注解还不够,需配套调整:
-
MySQL 示例:在 JDBC URL 中启用只读支持,如
?useSSL=false&serverTimezone=UTC&cachePrepStmts=true,并确认驱动版本 ≥ 8.0.28(对setReadOnly响应更可靠) -
HikariCP 连接池:可配置
connection-init-sql=SET SESSION TRANSACTION READ ONLY(MySQL)或connection-init-sql=SET default_transaction_read_only = on(PostgreSQL) -
JPA/Hibernate:设置
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true可强化只读语义;避免在只读方法中调用em.flush()或修改实体状态
什么时候加 readOnly = true 真正有意义?
不是所有查询都适合加,重点看三点:
- 方法内确定不修改任何数据(不含 save/update/delete、不含级联持久化、不含 flush/clear)
- 调用链路不混入写操作(比如该方法被另一个
@Transactional方法调用,且外层事务非只读,则传播行为可能覆盖内层设置) - 高并发读场景下,数据库已存在读写分离或连接池有只读节点路由能力(此时
readOnly = true可辅助路由到从库)
简单查单条、列表、统计等纯查询逻辑,加上它 + 配合好基础设施,才能把“只读”从语义变成性能红利。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











