Вопросы по 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 на позиции джуна?
Да, но в более узком объёме: аннотации, бины, простые контроллеры и репозитории. Тонкости прокси и транзакционных границ обычно всплывают на уровне выше.