What is EntityManager and How Does It Differ from Session
Since Hibernate 5.2+, Session directly extends EntityManager:
EntityManager and Session are the two primary interfaces used by Java applications to interact with an Object-Relational Mapping (ORM) persistence context. EntityManager is the vendor-neutral standard defined by the Jakarta Persistence (JPA) specification, whereas Session is the native, proprietary interface of the Hibernate framework, offering advanced low-level features that extend beyond the JPA specification.
🟢 Junior Level
Definitions and Interface Hierarchy
EntityManager(JPA): The official standard interface of Jakarta EE (jakarta.persistence.EntityManager). It provides a portable, provider-independent contract implemented by all JPA-compliant engines (Hibernate, EclipseLink, OpenJPA).Session(Hibernate): Hibernate’s native core interface (org.hibernate.Session), which predates the initial JPA specification.
Since Hibernate 5.2+, Session directly extends EntityManager:
package org.hibernate;
public interface Session extends jakarta.persistence.EntityManager, AutoCloseable {
// Advanced proprietary Hibernate methods
}
ORM INTERFACE HIERARCHY
┌────────────────────────────────────────┐
│ jakarta.persistence.EntityManager │ (JPA Standard)
└───────────────────▲────────────────────┘
│ extends
┌───────────────────┴────────────────────┐
│ org.hibernate.Session │ (Hibernate Native API)
└───────────────────▲────────────────────┘
│ implements
┌───────────────────┴────────────────────┐
│ org.hibernate.internal.SessionImpl │ (Concrete Implementation)
└────────────────────────────────────────┘
Accessing Native Hibernate Features via unwrap()
If your application interacts with the standard JPA EntityManager, you can obtain the underlying native Hibernate Session using the unwrap() method:
@PersistenceContext
private EntityManager entityManager;
public void demonstrateUnwrap() {
// Unwraps the underlying native Hibernate Session:
Session session = entityManager.unwrap(Session.class);
// Proprietary Hibernate features are now directly accessible:
boolean connected = session.isConnected();
}
Method Equivalency Matrix
| Operation | JPA Standard (EntityManager) |
Native Hibernate (Session) |
|---|---|---|
| Save New Entity | persist(entity) (returns void) |
persist(entity) or legacy save(entity) (returns Serializable ID) |
| Merge Detached Entity | merge(entity) |
merge(entity) or legacy update() / saveOrUpdate() |
| Remove Entity | remove(entity) |
remove(entity) or legacy delete(entity) |
| Find by Primary Key | find(Class, id) |
find(Class, id) or legacy get(Class, id) |
| Get Lazy Reference (Proxy) | getReference(Class, id) |
load(Class, id) |
| Clear Persistence Context | clear() |
clear() |
| Detach Entity from L1 | detach(entity) |
detach(entity) or legacy evict(entity) |
🟡 Middle Level
Exclusive Native Session Features Beyond JPA
While EntityManager handles the vast majority of web application requirements, Session provides specialized optimization tools:
1. Smart Batch Multi-Loading (byMultipleIds)
Standard JPA requires writing JPQL to load multiple IDs: SELECT u FROM User u WHERE u.id IN (:ids). This query always executes an SQL SELECT against the database, bypassing L1/L2 caches.
Hibernate’s Session provides 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) // Checks L1 and L2 cache BEFORE querying the database!
.multiLoad(ids);
If entities with IDs 1 and 2 already reside in the L1/L2 cache, Hibernate constructs an SQL query querying only the missing records: WHERE id IN (3, 4, 5).
2. Direct JDBC Connection Access (doWork)
When you need to execute high-performance low-level JDBC batch operations or database-specific commands:
Session session = entityManager.unwrap(Session.class);
session.doWork(connection -> {
try (PreparedStatement ps = connection.prepareStatement("COPY users TO STDOUT")) {
// Direct socket streaming in PostgreSQL or Oracle, bypassing ORM overhead
}
});
3. Dynamic Session Filters (Hibernate Filters)
Allows enabling global filtering clauses (such as Multi-tenancy or Soft-Delete) across all queries executed within the session:
Session session = entityManager.unwrap(Session.class);
session.enableFilter("tenantFilter").setParameter("tenantId", "EU_REGION");
// Every subsequent query automatically appends: WHERE tenant_id = 'EU_REGION'
List<Order> orders = entityManager.createQuery("FROM Order", Order.class).getResultList();
🔴 Senior Level
Thread-Safety & Spring Architecture: SharedEntityManagerCreator
Neither EntityManager nor Session is thread-safe. They encapsulate an open JDBC connection and the non-thread-safe PersistenceContext (L1 Cache).
Yet in Spring applications, developers inject EntityManager as a singleton bean dependency without concurrency issues:
@Service
public class UserService {
@PersistenceContext
private EntityManager entityManager; // How does this work across concurrent HTTP threads?
}
Spring’s Internal Architecture:
- The injected
entityManageris NOT a directSessionImplinstance. It is a dynamic proxy created bySharedEntityManagerCreator. - Whenever any method (e.g.,
entityManager.find()) is called, the proxy invocation handler (SharedEntityManagerInvocationHandler):- Queries Spring’s
TransactionSynchronizationManagerfor the current thread (ThreadLocal). - Retrieves the physical, thread-bound
EntityManagerassociated with the active@Transactionalboundary. - If no transaction exists, the proxy creates an ad-hoc
EntityManager, executes the query, and closes it immediately.
- Queries Spring’s
- This architecture guarantees that each concurrent worker thread operates on its own isolated
SessionImplinstance.
SPRING ENTITYMANAGER PROXY RESOLUTION
[ HTTP Worker Thread 1 ] ──┐
▼
┌────────────────────────────────────────────────────────┐
│ Singleton Proxy (SharedEntityManagerInvocationHandler)│
└────────────────────────────────────────────────────────┘
│
ThreadLocal ├──> [ Thread 1: Physical SessionImpl @0x123 ]
Resolution │
└──> [ Thread 2: Physical SessionImpl @0x456 ]
High-Throughput Cursor Streaming: ScrollableResults
Loading 1,000,000 entities via standard JPA getResultList() triggers an immediate OutOfMemoryError because JPA attempts to instantiate the entire collection in heap memory.
Hibernate’s Session solves this via server-side database cursor streaming:
Session session = entityManager.unwrap(Session.class);
try (ScrollableResults<User> scroll = session.createQuery("FROM User", User.class)
.setFetchSize(100) // Fetches chunks of 100 rows from the database cursor
.scroll(ScrollMode.FORWARD_ONLY)) {
int count = 0;
while (scroll.next()) {
User user = scroll.get();
process(user);
if (++count % 100 == 0) {
session.flush();
session.clear(); // Releases L1 cache memory to prevent JVM heap exhaustion
}
}
}
4 Tricky Questions
1. Is Session a decorator (wrapper) around EntityManager, or vice versa? What is their exact relationship in Hibernate 6?
Answer:
- Neither is a wrapper. Since Hibernate 5.2,
org.hibernate.Sessiondirectly extendsjakarta.persistence.EntityManager. - The concrete engine class
org.hibernate.internal.SessionImplimplements bothSessionandEntityManagersimultaneously. - Therefore, invoking
entityManager.unwrap(Session.class)performs a direct, safe reference type downcast without allocating any wrapper or decorator objects.
2. What is the fundamental difference between legacy session.save(entity) and entityManager.persist(entity) for an entity with a SEQUENCE generator?
Answer:
entityManager.persist(entity)has avoidreturn type. It fetches the next sequence value from the database, populates the entity’s ID in memory, and defers the physical SQLINSERTuntil theflush()phase.- The legacy
session.save(entity)has the signatureSerializable save(Object object). It is contractually required to immediately return the generated identifier to the caller. - While both query the database sequence,
save()is not part of the JPA specification and bypasses standardized JPA cascade rules. In modern applications,session.save()is deprecated in favor ofpersist().
3. Why can invoking sessionFactory.getCurrentSession() in a standalone application without Spring throw HibernateException: No CurrentSessionContext configured?
Answer:
- The
getCurrentSession()method requires a strategy to bind the active session to the current execution context (such as a thread or a JTA transaction). - This strategy is defined by the
hibernate.current_session_context_classconfiguration property. - In standalone Hibernate, you must explicitly configure this property to
thread(binding toThreadLocal) orjta. - In Spring Boot applications, Spring configures this property automatically by registering
SpringSessionContext, which harmonizes Hibernate sessions with Spring’sPlatformTransactionManager.
4. What happens if you manually invoke session.close() on a session obtained via entityManager.unwrap(Session.class) inside a method marked with @Transactional?
Answer:
- Because the
EntityManageris managed by Spring’s transactional infrastructure, manually callingsession.close()immediately closes the underlying physical JDBC connection and clears the persistence context. - Upon exiting the method, Spring’s transaction interceptor attempts to perform the pre-commit flush and commit.
- Because the session has been closed, Spring throws an unrecoverable exception:
TransactionSystemException: Could not commit JPA transaction; nested exception is org.hibernate.SessionException: Session is closed!. - Golden Rule: Never manually close an
EntityManagerorSessionmanaged by Spring container infrastructure.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“
EntityManageris the standard, portable JPA interface defined by Jakarta EE, whereasSessionis the native Hibernate interface that extendsEntityManager. For standard domain persistence, you should always program againstEntityManagerto maintain vendor neutrality and clean architectural boundaries. You can access native Hibernate features at any time viaentityManager.unwrap(Session.class)for specialized operations: multi-ID batch loading with cache verification (byMultipleIds), direct JDBC streaming viadoWork(), cursor scrolling withScrollableResults, or dynamic filtering with Hibernate Filters.”
Key Production Metrics & Health Checks
- Session Open / Close Balance: Monitors the lifecycle of Hibernate sessions to guarantee that connections are closed properly without leaking HikariCP pool slots.
- Active Transactions per Thread: Verifies that Spring’s
ThreadLocalcontext correctly binds one active persistence context per worker thread.
Red Flags to Avoid
- ❌ “Session is an obsolete interface that is no longer used inside Hibernate.” — Hibernate’s entire internal core (
SessionImpl) is built aroundSession. - ❌ “EntityManager is thread-safe because Spring injects it into Singleton services.” —
EntityManageris strictly single-threaded; Spring uses a dynamic proxy (SharedEntityManagerCreator) that routes calls to thread-bound instances. - ❌ “You should always call
session.close()at the end of every Spring@Transactionalmethod.” — Manually closing a Spring-managed session breaks the transaction commit phase and throwsSession is closed. - ❌ “Session wraps EntityManager using the Decorator design pattern.” — In Hibernate,
Sessioninherits fromEntityManager, andSessionImplimplements both directly.
Related Topics
- Describe the Entity Lifecycle in Hibernate — Managed state lifecycle
- What is the Difference Between persist() and merge() — JPA persistence operations
- What is Dirty Checking in Hibernate — Change detection
- How Does the Flush Mechanism Work in Hibernate — Session flush mechanics
- What is the First-Level Cache in Hibernate — Persistence context storage