Как Spring выполняет запросы к базе данных через репозиторий
Что проверяют
Знаете ли вы, что за интерфейсом репозитория нет написанного вами класса, и что происходит между вызовом метода и SQL. Следом часто спрашивают, откуда у репозитория транзакции и почему save() иногда делает лишний SELECT.
Как отвечать
Проследите путь вызова по шагам:
- старт приложения: Spring Data находит интерфейсы, унаследованные от
Repository(JpaRepository,CrudRepository), и для каждого создаёт прокси черезJpaRepositoryFactory. Аннотация@Repositoryна интерфейсе для этого не нужна; - разбор методов: там же, на старте, для каждого метода выбирается стратегия. Если есть
@Query, берётся он; если есть именованный запрос, берётся он; иначе имя вродеfindByLastNameAndAgeGreaterThanразбирается на части и превращается в JPA-запрос. Ошибка в имени метода роняет контекст при запуске, а не при вызове; - CRUD-методы:
save,findById,deleteByIdуходят вSimpleJpaRepository, готовую реализацию поверхEntityManager; - выполнение: прокси прогоняет вызов через перехватчики (транзакции, перевод исключений в
DataAccessException), затемEntityManagerпередаёт запрос Hibernate. SQL под конкретную базу генерирует уже Hibernate, соединение берётся из пула, в Spring Boot это HikariCP.
Про транзакции: на SimpleJpaRepository стоит @Transactional(readOnly = true), изменяющие методы помечены отдельно, поэтому save без внешней транзакции всё равно транзакционен. Но бизнес-операцию из нескольких вызовов оборачивают в @Transactional на сервисе, иначе каждый вызов коммитится сам по себе. Для UPDATE и DELETE через @Query нужен ещё @Modifying.
Где спотыкаются
Говорят, что Spring сам генерирует SQL из имени метода. Spring Data строит JPA-запрос, а SQL собирает Hibernate.
Не знают, как работает save: для новой сущности (id равен null) вызывается persist, иначе merge, а он сначала читает строку из базы. Отсюда лишний SELECT при вручную заданном идентификаторе, лечится через Persistable или поле @Version.
Ставят @Transactional на приватный метод или вызывают аннотированный метод из того же класса. Прокси такой вызов не перехватывает, и транзакции нет.