Что такое EntityManager и чем он отличается от Session
Начиная с версии Hibernate 5.2+, интерфейс Session напрямую наследует EntityManager:
EntityManager и Session — это два основных интерфейса для взаимодействия приложения с объектно-реляционным отображением (ORM) в Java. EntityManager представляет собой официальный стандарт спецификации JPA (Jakarta Persistence API), в то время как Session — это исторический, вендор-специфичный интерфейс фреймворка Hibernate, расширяющий стандартный функционал мощными низкоуровневыми возможностями.
🟢 Junior Level
Что такое EntityManager и Session
EntityManager(JPA): Стандартный интерфейс Java EE / Jakarta EE (jakarta.persistence.EntityManager). Он определяет единый платформонезависимый контракт работы с сущностями, который обязан поддерживать любой JPA-совместимый провайдер (Hibernate, EclipseLink, OpenJPA).Session(Hibernate): Собственный интерфейс библиотеки Hibernate (org.hibernate.Session), появившийся задолго до первой спецификации JPA.
Начиная с версии Hibernate 5.2+, интерфейс Session напрямую наследует EntityManager:
package org.hibernate;
public interface Session extends jakarta.persistence.EntityManager, AutoCloseable {
// Дополнительные фирменные методы Hibernate
}
ИЕРАРХИЯ ИНТЕРФЕЙСОВ ORM
┌────────────────────────────────────────┐
│ jakarta.persistence.EntityManager │ (JPA Стандарт)
└───────────────────▲────────────────────┘
│ extends
┌───────────────────┴────────────────────┐
│ org.hibernate.Session │ (Hibernate API)
└───────────────────▲────────────────────┘
│ implements
┌───────────────────┴────────────────────┐
│ org.hibernate.internal.SessionImpl │ (Реализация)
└────────────────────────────────────────┘
Как перейти от JPA к возможностям Hibernate
Если в приложении используется стандартный EntityManager, получить доступ к вендорскому API Hibernate можно в одну строчку через метод unwrap():
@PersistenceContext
private EntityManager entityManager;
public void demonstrateUnwrap() {
// Извлекаем нативную сессию Hibernate:
Session session = entityManager.unwrap(Session.class);
// Теперь доступны фирменные методы Hibernate
boolean connected = session.isConnected();
}
Сравнительная таблица методов
| Задача | Стандарт JPA (EntityManager) |
Нативный Hibernate (Session) |
|---|---|---|
| Сохранение новой сущности | persist(entity) (возвращает void) |
persist(entity) или legacy save(entity) (возвращает Serializable ID) |
| Слияние отсоединенной сущности | merge(entity) |
merge(entity) или legacy update() / saveOrUpdate() |
| Удаление сущности | remove(entity) |
remove(entity) или legacy delete(entity) |
| Поиск по первичному ключу | find(Class, id) |
find(Class, id) или get(Class, id) |
| Получение ленивой ссылки (Proxy) | getReference(Class, id) |
load(Class, id) |
| Очистка контекста | clear() |
clear() |
| Отсоединение сущности | detach(entity) |
detach(entity) или legacy evict(entity) |
🟡 Middle Level
Эксклюзивные возможности Session, отсутствующие в JPA
Хотя EntityManager покрывает 95% типичных задач веб-приложения, Session предоставляет уникальные инструменты оптимизации:
1. Пакетная мульти-загрузка по списку ID (multiLoad)
Стандартный JPA для загрузки пачки сущностей требует написания JPQL: SELECT u FROM User u WHERE u.id IN (:ids). Этот запрос всегда идет в базу данных, игнорируя кэш L1/L2.
Hibernate Session предоставляет интеллектуальный метод byMultipleIds():
Session session = entityManager.unwrap(Session.class);
List<Long> ids = List.of(1L, 2L, 3L, 4L, 5L);
List<User> users = session.byMultipleIds(User.class)
.enableCheckCache(true) // Проверяет L1 и L2 кэш перед отправкой SQL!
.multiLoad(ids);
Если сущности с ID 1 и 2 уже лежат в кэше L1, Hibernate сформирует SQL строго для оставшихся: WHERE id IN (3, 4, 5).
2. Прямой доступ к JDBC Connection (doWork)
Когда требуется выполнить сложный низкоуровневый JDBC-код или вызвать специфические процедуры СУБД:
Session session = entityManager.unwrap(Session.class);
session.doWork(connection -> {
try (PreparedStatement ps = connection.prepareStatement("COPY users TO STDOUT")) {
// Прямая работа с сокетом PostgreSQL / Oracle без накладных расходов ORM
}
});
3. Динамические фильтры данных (Hibernate Filters)
Позволяют глобально включить условие фильтрации (например, Soft Delete или Multi-tenancy) на уровне всей сессии:
Session session = entityManager.unwrap(Session.class);
session.enableFilter("tenantFilter").setParameter("tenantId", "EU_REGION");
// Теперь АБСОЛЮТНО ВСЕ выборки автоматически получают условие WHERE tenant_id = 'EU_REGION'
List<Order> orders = entityManager.createQuery("FROM Order", Order.class).getResultList();
🔴 Senior Level
Потокобезопасность и архитектура Spring: SharedEntityManagerCreator
И EntityManager, и Session категорически не потокобезопасны. Они инкапсулируют состояние открытого JDBC-соединения и локальную карту PersistenceContext (L1 Cache).
Однако в Spring сервисах мы спокойно инжектим EntityManager как поле в Singleton-бин:
@Service
public class UserService {
@PersistenceContext
private EntityManager entityManager; // Как это работает в многопоточной среде?!
}
Внутренняя архитектура Spring:
- Поле
entityManagerсодержит НЕ реальныйSessionImpl, а динамический прокси-объект, созданный классомSharedEntityManagerCreator. - При вызове любого метода (например,
entityManager.find()) прокси-обработчик (SharedEntityManagerInvocationHandler):- Проверяет
TransactionSynchronizationManagerтекущего потока (ThreadLocal). - Извлекает реальный
EntityManager, привязанный к текущей активной транзакции@Transactional. - Если транзакция не открыта, прокси создает временный
EntityManager, выполняет операцию и тут же закрывает его.
- Проверяет
- Таким образом, каждый параллельный поток веб-сервера работает со своим собственным изолированным экземпляром
SessionImpl.
ИНЖЕКЦИЯ ENTITYMANAGER В SPRING
[ Поток HTTP-1 ] ──┐
▼
┌────────────────────────────────────────────────────────┐
│ Singleton Proxy (SharedEntityManagerInvocationHandler)│
└────────────────────────────────────────────────────────┘
│
ThreadLocal ├──> [ Thread 1: Реальный SessionImpl @0x123 ]
Resolution │
└──> [ Thread 2: Реальный SessionImpl @0x456 ]
Потоковая обработка огромных выборок: ScrollableResults
Попытка загрузить 1 000 000 строк через стандартный JPA getResultList() приведет к немедленному OutOfMemoryError, так как JPA пытается материализовать весь список в память.
Hibernate Session решает эту задачу с помощью курсорного стриминга:
Session session = entityManager.unwrap(Session.class);
try (ScrollableResults<User> scroll = session.createQuery("FROM User", User.class)
.setFetchSize(100) // Читаем пачками по 100 строк из курсора БД
.scroll(ScrollMode.FORWARD_ONLY)) {
int count = 0;
while (scroll.next()) {
User user = scroll.get();
process(user);
if (++count % 100 == 0) {
session.flush();
session.clear(); // Очищаем L1 кэш для предотвращения OOM
}
}
}
4 Tricky Questions
1. Является ли Session оберткой (декоратором) над EntityManager, или наоборот? Какова их связь в Hibernate 6?
Ответ:
- Никаких внешних декораторов нет. Начиная с Hibernate 5.2, базовый интерфейс
org.hibernate.Sessionпрямо расширяетjakarta.persistence.EntityManager. - Реализующий их класс
SessionImplодновременно является и полноценнымEntityManager, иSession. - Поэтому вызов
entityManager.unwrap(Session.class)не создает новый объект-обертку, а выполняет обычное безопасное приведение типов (Downcasting) внутренней ссылки к интерфейсуSession.
2. В чем принципиальная разница между session.save(entity) и entityManager.persist(entity) для сущности со стратегией генерации ключа SEQUENCE?
Ответ:
entityManager.persist(entity)возвращаетvoid. При вызове он запрашивает очередное число из последовательности СУБД, присваивает ID объекту в памяти и откладывает выполнение SQLINSERTдо фазыflush().- Устаревший метод
session.save(entity)имеет сигнатуруSerializable save(Object object). Он обязан немедленно вернуть идентификатор вызывающему коду. - Хотя оба метода запрашивают последовательность без немедленного
INSERT, методsave()не входит в спецификацию JPA и не учитывает некоторые тонкости жизненного цикла каскадных транзакций JPA. В современном коде вызовsession.save()признан устаревшим (deprecated), вместо него следует использоватьpersist().
3. Почему прямой вызов sessionFactory.getCurrentSession() в приложении без Spring Framework может выбросить HibernateException: No CurrentSessionContext configured?
Ответ:
- Методу
getCurrentSession()требуется стратегия привязки сессии к контексту выполнения (к потоку или транзакции JTA). - За это отвечает свойство
hibernate.current_session_context_class. - В чистом Hibernate без Spring нужно вручную сконфигурировать значение
thread(привязка кThreadLocal) илиjta. - В Spring Framework настройка выполняется автоматически: Spring регистрирует собственный класс
SpringSessionContext, который синхронизирует Hibernate Session с менеджером транзакцийPlatformTransactionManager.
4. Что произойдет, если закрыть Session, полученную через entityManager.unwrap(Session.class).close(), внутри метода с аннотацией @Transactional?
Ответ:
- Поскольку в Spring
EntityManagerуправляется инфраструктурой транзакций, вызовsession.close()принудительно закроет физическое JDBC-соединение и очистит контекст постоянства. - При выходе из метода перехватчик
@Transactionalпопытается выполнить фазу коммита (flush()иcommit()). - Так как сессия уже закрыта, Spring выбросит исключение:
TransactionSystemException: Could not commit JPA transaction; nested exception is org.hibernate.SessionException: Session is closed!. - Правило: Никогда не закрывать вручную
EntityManagerилиSession, внедренные через Spring!
🎯 Шпаргалка для интервью
30-секундный ответ (Elevator Pitch)
«
EntityManager— это стандартный переносимый JPA-интерфейс, определенный спецификацией Jakarta EE.Session— это нативный интерфейс Hibernate, который в современных версиях (Hibernate 5.2+) напрямую наследуетEntityManager. В стандартных сценариях рекомендуется всегда использоватьEntityManagerради соблюдения абстракции и переносимости кода. Доступ кSessionполучают черезentityManager.unwrap(Session.class)только тогда, когда требуются специфические возможности Hibernate: пакетная загрузка по нескольким ID (multiLoad), прямое управление JDBC черезdoWork(), потоковый курсорScrollableResultsили динамические Hibernate Filters».
Ключевые метрики для Production-мониторинга
- Hibernate Session Open / Close Rate: соотношение открываемых и закрываемых сессий (гарантия отсутствия утечек соединений).
- Active Transactions per Thread: контроль корректной изоляции сессий в пуле потоков.
Красные флаги (Чего говорить НЕЛЬЗЯ)
- ❌ «Session — это устаревший интерфейс, который больше не используется внутри Hibernate» — Ложь. Вся внутренняя реализация ядра Hibernate (
SessionImpl) построена вокругSession. - ❌ «EntityManager потокобезопасен, потому что мы внедряем его через @Autowired или @PersistenceContext в Singleton-бины» — Сам
EntityManagerстрого однопоточен; потокобезопасность обеспечивает транзакционный прокси Spring (SharedEntityManagerCreator). - ❌ «Нужно всегда закрывать Session вручную через session.close() в конце метода сервиса» — В Spring это сломает транзакцию и вызовет
Session is closed. - ❌ «Session оборачивает EntityManager через паттерн Wrapper» — Нет, в Hibernate
SessionнаследуетEntityManager.