Вопросы по Spring и Hibernate на собеседовании
Spring спрашивают почти на каждом собесе Java-разработчика, и именно здесь чаще всего выясняется, что человек писал аннотации, не понимая, что за ними происходит. Транзакции и ленивая загрузка дают самый честный срез.
Что спрашивают
- +Контейнер: IoC и DI, скоупы бинов, жизненный цикл, циклические зависимости и как Spring их разруливает
- +Spring Boot и AOP: автоконфигурация, стартеры, прокси и почему self-invocation ломает аннотации
- +Транзакции: propagation и isolation, откат по проверяемым исключениям, что произойдёт при вызове @Transactional изнутри класса
- +Hibernate: ленивая и жадная загрузка, N+1, кеш первого уровня, состояния сущности и detached-объекты
- +Spring MVC и REST: диспетчер, обработка исключений, валидация запросов
Из чего состоит тема
Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, что просели.
- ORM-маппинг21
- Spring Data и транзакции19
- Spring Boot и AOP18
- IoC и DI17
- Spring MVC и REST16
- Транзакции и блокировки16
- Загрузка и N+115
- Состояния entity15
- Кэш L1/L213
- Бины: цикл и скоупы12
Разборы подтем
Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.
- Spring Boot и AOP: автоконфигурация и прокси18 вопросов
- Spring Data и транзакции19 вопросов
- Кэш первого и второго уровня13 вопросов
- Состояния entity в Hibernate15 вопросов
- Ленивая загрузка и N+1 в Hibernate15 вопросов
- ORM-маппинг в Hibernate21 вопросов
- Транзакции и блокировки в Hibernate16 вопросов
- Бины Spring: цикл и скоупы12 вопросов
- IoC и внедрение зависимостей17 вопросов
- Spring MVC и REST16 вопросов
Примеры вопросов с разбором
- Что такое кэш первого уровня (L1) в Hibernate?A)Кэш в пределах одной сессии — всегда включён, гарантирует идентичность сущностейB)Общий для всех сессий и потоков кэш, который надо явно включать через отдельный провайдер вроде EhcacheC)Кэш скомпилированных SQL-запросов, переиспользуемый между разными приложениями на одном сервере БДD)Дисковый кэш, куда Hibernate сбрасывает загруженные сущности, чтобы пережить перезапуск приложения
показать ответ и разбор
+A)Кэш в пределах одной сессии — всегда включён, гарантирует идентичность сущностей// разбор: Кэш ПЕРВОГО уровня (L1) — это и есть контекст персистентности (Session/EntityManager): он ВСЕГДА включён, его нельзя отключить, и он локален для ОДНОЙ сессии/единицы работы. В нём хранятся все managed-сущности, поэтому повторный find по тому же id в этой сессии не идёт в БД и возвращает тот же объект. При закрытии сессии L1 очищается. Это не разделяемый и не настраиваемый кэш — им управляет сам Hibernate в пределах транзакции. Настраиваемый разделяемый кэш — это уже L2.
- Какие состояния жизненного цикла бывают у сущности JPA?A)Transient, Persistent (managed), Detached, RemovedB)Created, Running, Paused, Stopped — как у обычного потока исполнения в многопоточном приложенииC)Draft, Published, Archived — соответствующие стадиям публикации записи в базе данных приложенияD)Open, Closed, Committed, RolledBack — состояния, наследуемые сущностью от текущей транзакции
показать ответ и разбор
+A)Transient, Persistent (managed), Detached, Removed// разбор: Сущность проходит четыре состояния: TRANSIENT — только что создана через new, у неё нет id и контекст её не отслеживает; PERSISTENT/managed — привязана к контексту персистентности (EntityManager), Hibernate отслеживает её изменения и синхронизирует с БД; DETACHED — была persistent, но контекст закрылся (или её выселили) — изменения больше не отслеживаются; REMOVED — помечена на удаление. Переходы: persist (transient→persistent), remove (→removed), закрытие сессии (persistent→detached), merge (detached→managed-копия). Понимание состояний объясняет, почему изменения то сохраняются сами, то нет.
- Чем LAZY-загрузка связи отличается от EAGER?A)LAZY грузит связь при первом обращении; EAGER — сразу вместе с сущностьюB)LAZY загружает связанные данные только из кэша, а EAGER — напрямую из базы данных зановоC)LAZY и EAGER отличаются лишь порядком колонок в SELECT, но обе грузят связь одним запросомD)EAGER откладывает загрузку до обращения, а LAZY подгружает всё содержимое связи заранее при чтении
показать ответ и разбор
+A)LAZY грузит связь при первом обращении; EAGER — сразу вместе с сущностью// разбор: LAZY: связь не грузится вместе с сущностью — вместо неё стоит прокси/обёртка, а реальный SELECT выполняется при ПЕРВОМ обращении к связи (если сессия ещё открыта). EAGER: связь подгружается СРАЗУ при загрузке корневой сущности (обычно джойном или отдельным запросом). LAZY экономит, когда связь не нужна, но рискует LazyInitializationException (доступ после закрытия сессии) и N+1 (в цикле). EAGER гарантирует наличие данных, но тянет лишнее и провоцирует избыточные запросы. Обычно ставят LAZY и подгружают что нужно явно (JOIN FETCH).
- Что такое ORM (например, Hibernate)?A)Отображение объектов на строки таблиц — работа с БД через объекты, а не SQLB)Отдельная база данных, встроенная в приложение, которая заменяет собой внешнюю СУБДC)Инструмент, который автоматически проектирует схему базы данных по требованиям бизнес-аналитикаD)Протокол сетевого обмена между приложением и базой данных вместо стандартного драйвера JDBC
показать ответ и разбор
+A)Отображение объектов на строки таблиц — работа с БД через объекты, а не SQL// разбор: ORM (Object-Relational Mapping) — прослойка, отображающая объекты приложения на строки реляционных таблиц: класс ↔ таблица, поле ↔ колонка, связь между объектами ↔ внешний ключ. Разработчик работает с СУЩНОСТЯМИ и их графами, а ORM сам генерирует SQL (SELECT/INSERT/UPDATE/DELETE), управляет идентичностью объектов, кэшем и транзакциями. Hibernate — самая популярная реализация спецификации JPA. Плюсы — меньше шаблонного JDBC; риски — скрытые запросы (N+1), утечки абстракции (нужно понимать, какой SQL порождается).
- Что такое оптимистичная блокировка (@Version) в JPA?A)Поле-версия проверяется при UPDATE; конфликт параллельной правки → OptimisticLockExceptionB)Блокировка строки в БД на всё время транзакции, из-за которой другие транзакции ждут её завершенияC)Механизм, который оптимистично предполагает отсутствие ошибок и отключает проверки на конфликтыD)Кэш версий сущностей в памяти, ускоряющий чтение за счёт хранения нескольких копий одной строки
показать ответ и разбор
+A)Поле-версия проверяется при UPDATE; конфликт параллельной правки → OptimisticLockException// разбор: Оптимистичная блокировка НЕ держит строку заблокированной: она добавляет сущности поле @Version (int/long/timestamp). При UPDATE Hibernate включает в WHERE проверку версии (... WHERE id=? AND version=?) и инкрементирует её. Если между чтением и записью КТО-ТО другой уже изменил строку (версия не совпала — 0 обновлённых строк), бросается OptimisticLockException — «кто-то опередил, перечитай и повтори». Подходит для сценариев с редкими конфликтами (веб-формы): не блокирует конкурентов, а обнаруживает конфликт по факту. Дёшево и хорошо масштабируется.
- Какой скоуп у бина Spring по умолчанию?A)singleton — один экземпляр на контейнерB)Prototype — новый экземпляр создаётся при каждом внедрении и обращении к бину из контейнераC)Request — по одному экземпляру бина на каждый входящий HTTP-запрос к приложениюD)Thread — отдельный экземпляр бина для каждого потока, обращающегося к контейнеру за ним
показать ответ и разбор
+A)singleton — один экземпляр на контейнер// разбор: По умолчанию бины — SINGLETON: контейнер создаёт ОДИН экземпляр и отдаёт его всем, кто его запрашивает/внедряет (один на контейнер, не обязательно на всю JVM). Другие скоупы: prototype (новый объект на каждое получение), а в вебе — request, session, application. Важное следствие singleton: общий экземпляр разделяется потоками, поэтому хранить в нём изменяемое состояние небезопасно (см. потокобезопасность). Скоуп задаётся @Scope.
- Что такое инверсия управления (IoC) в Spring?A)Создание и связывание объектов берёт на себя контейнер, а не сам кодB)Автоматическое обращение порядка выполнения методов класса на противоположный при старте контейнераC)Механизм, при котором каждый объект сам создаёт все свои зависимости через оператор new внутри себяD)Способ инвертировать наследование: родитель начинает зависеть от своих потомков, а не наоборот
показать ответ и разбор
+A)Создание и связывание объектов берёт на себя контейнер, а не сам код// разбор: Инверсия управления — принцип, при котором управление созданием объектов и их зависимостями передаётся ВНЕШНЕМУ контейнеру (Spring), а не пишется руками через new внутри классов. Контейнер сам инстанцирует бины, внедряет их зависимости (DI — конкретная реализация IoC) и управляет жизненным циклом. Класс лишь ОБЪЯВЛЯЕТ, что ему нужно, и получает готовое. Это снижает связанность, упрощает подмену реализаций и тестирование (можно внедрить мок).
- Что делает автоконфигурация (auto-configuration) Spring Boot?A)Сама настраивает бины по наличию зависимостей в classpath и свойствB)Автоматически пишет исходный код приложения по описанию требований и компилирует его при стартеC)Отключает всю ручную конфигурацию: в Spring Boot не получится переопределять создаваемые бины вручнуюD)Раздаёт приложение по нескольким серверам, автоматически масштабируя его под текущую нагрузку
показать ответ и разбор
+A)Сама настраивает бины по наличию зависимостей в classpath и свойств// разбор: Автоконфигурация — сердце «магии» Boot: по тому, что ЕСТЬ в classpath (стартеры-зависимости) и в свойствах, Boot САМ создаёт разумные бины по умолчанию — увидел spring-boot-starter-data-jpa и драйвер БД → настроил DataSource, EntityManager, транзакции. Реализовано через @Conditional-условия (@ConditionalOnClass/OnMissingBean/OnProperty). Ключевой принцип: ваши собственные бины и свойства ПЕРЕОПРЕДЕЛЯЮТ дефолты (автоконфиг «отступает», если бин уже задан). Так убирают тонны шаблонной XML/Java-конфигурации.
- Как в Spring Data JPA создать запрос findByEmail(String email)?A)Объявить метод в интерфейсе-репозитории — Spring сгенерирует реализацию по имениB)Написать полную реализацию метода с JDBC-кодом вручную в классе, реализующем интерфейс репозиторияC)Создать хранимую процедуру в базе данных с именем findByEmail и вызвать её напрямую из кодаD)Скопировать SQL-запрос в отдельный XML-файл маппинга, который Spring загрузит по имени метода
показать ответ и разбор
+A)Объявить метод в интерфейсе-репозитории — Spring сгенерирует реализацию по имени// разбор: Spring Data JPA генерирует реализацию репозитория из ИНТЕРФЕЙСА: достаточно объявить метод по соглашению об именовании (findByEmail, findByAgeGreaterThan, findByLastNameOrderByFirstName), и Spring разберёт имя и построит JPQL/запрос сам — без кода. Для сложных запросов есть @Query (JPQL или nativeQuery), Specification/Criteria, QueryDSL. Базовый CrudRepository/JpaRepository уже даёт save/findById/findAll/delete. Это резко сокращает шаблонный DAO-код. Слишком длинные производные имена — сигнал перейти на @Query.
это 9 из 162
Ещё 153 вопросов по теме — в тренажёре, с движком повторения
Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы приходят сессиями, а подтему, на которой ты споткнулся, движок принесёт снова: завтра, через три дня, через неделю. Бесплатно, с дневным лимитом вопросов.
Частые вопросы
Что спрашивают про Spring чаще всего?
Как работает внедрение зависимостей, чем отличаются скоупы, как устроены транзакции и почему аннотация не сработала при вызове метода изнутри того же класса.
Нужно ли знать Hibernate, если в проекте только JDBC?
Базово нужно: ORM спрашивают почти всегда, а вопросы про N+1 и ленивую загрузку задают даже тем, кто пишет запросы руками. Достаточно понимать модель работы и типичные проблемы.
Спрашивают ли про Spring на позиции джуна?
Да, но в более узком объёме: аннотации, бины, простые контроллеры и репозитории. Тонкости прокси и транзакционных границ обычно всплывают на уровне выше.