Що таке 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.