Можно ли выполнить SELECT запрос без открытия транзакции в базе данных
Что проверяют
Понимаете ли вы, что транзакция есть всегда, вопрос только в том, кто её открывает: база, JDBC-драйвер или Spring. Следом обычно спрашивают, зачем тогда ставить @Transactional(readOnly = true) на методы, которые только читают.
Как отвечать
С точки зрения базы SELECT без транзакции не бывает: PostgreSQL, MySQL с InnoDB и Oracle выполняют его в неявной транзакции. В Java это выглядит так:
- JDBC:
Connectionпо умолчанию в режимеautoCommit = true, каждый оператор становится отдельной транзакцией. HikariCP держит тот же дефолт (auto-commit: true); - Spring:
@Transactionalвыключает автокоммит на время метода, и все запросы внутри видят одну транзакцию.readOnly = trueпереводит flush Hibernate в ручной режим, отключает dirty checking для загруженных сущностей и передаёт драйверу подсказку read-only; - Spring Data JPA: методы
SimpleJpaRepositoryуже помечены@Transactional(readOnly = true), поэтому даже «голый»findAll()идёт в транзакции; - согласованность: два SELECT подряд в режиме
READ COMMITTEDмогут увидеть разные данные. Если отчёт собирается из нескольких запросов, нужна одна транзакция сIsolation.REPEATABLE_READ.
Отдельно про блокирующее чтение: SELECT ... FOR UPDATE в автокоммите бессмыслен, блокировка снимается сразу после оператора, и защитить последующую запись она уже не может.
Где спотыкаются
Говорят «SELECT без транзакции быстрее», не понимая, что транзакция всё равно есть. Разница лишь в том, кто и когда её фиксирует, а долгая явная транзакция на чтение, наоборот, дольше держит соединение из пула.
Читают сущности вне @Transactional и получают LazyInitializationException при обращении к ленивой коллекции. Затем включают open-in-view или FetchType.EAGER, вместо того чтобы описать нужную выборку через JOIN FETCH или @EntityGraph.
Ставят @Transactional на private-метод или вызывают аннотированный метод из того же класса. Прокси Spring такой вызов не перехватывает, и транзакции нет.