💾 Section 16 · Question #16

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:

  1. The injected entityManager is NOT a direct SessionImpl instance. It is a dynamic proxy created by SharedEntityManagerCreator.
  2. Whenever any method (e.g., entityManager.find()) is called, the proxy invocation handler (SharedEntityManagerInvocationHandler):
    • Queries Spring’s TransactionSynchronizationManager for the current thread (ThreadLocal).
    • Retrieves the physical, thread-bound EntityManager associated with the active @Transactional boundary.
    • If no transaction exists, the proxy creates an ad-hoc EntityManager, executes the query, and closes it immediately.
  3. This architecture guarantees that each concurrent worker thread operates on its own isolated SessionImpl instance.
                    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:

  1. Neither is a wrapper. Since Hibernate 5.2, org.hibernate.Session directly extends jakarta.persistence.EntityManager.
  2. The concrete engine class org.hibernate.internal.SessionImpl implements both Session and EntityManager simultaneously.
  3. 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:

  1. entityManager.persist(entity) has a void return type. It fetches the next sequence value from the database, populates the entity’s ID in memory, and defers the physical SQL INSERT until the flush() phase.
  2. The legacy session.save(entity) has the signature Serializable save(Object object). It is contractually required to immediately return the generated identifier to the caller.
  3. 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 of persist().

3. Why can invoking sessionFactory.getCurrentSession() in a standalone application without Spring throw HibernateException: No CurrentSessionContext configured?

Answer:

  1. The getCurrentSession() method requires a strategy to bind the active session to the current execution context (such as a thread or a JTA transaction).
  2. This strategy is defined by the hibernate.current_session_context_class configuration property.
  3. In standalone Hibernate, you must explicitly configure this property to thread (binding to ThreadLocal) or jta.
  4. In Spring Boot applications, Spring configures this property automatically by registering SpringSessionContext, which harmonizes Hibernate sessions with Spring’s PlatformTransactionManager.

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:

  1. Because the EntityManager is managed by Spring’s transactional infrastructure, manually calling session.close() immediately closes the underlying physical JDBC connection and clears the persistence context.
  2. Upon exiting the method, Spring’s transaction interceptor attempts to perform the pre-commit flush and commit.
  3. 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!.
  4. Golden Rule: Never manually close an EntityManager or Session managed by Spring container infrastructure.

🎯 Interview Cheat Sheet

30-Second Elevator Pitch

“EntityManager is the standard, portable JPA interface defined by Jakarta EE, whereas Session is the native Hibernate interface that extends EntityManager. For standard domain persistence, you should always program against EntityManager to maintain vendor neutrality and clean architectural boundaries. You can access native Hibernate features at any time via entityManager.unwrap(Session.class) for specialized operations: multi-ID batch loading with cache verification (byMultipleIds), direct JDBC streaming via doWork(), cursor scrolling with ScrollableResults, 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 ThreadLocal context 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 around Session.
  • ❌ “EntityManager is thread-safe because Spring injects it into Singleton services.” — EntityManager is 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 @Transactional method.” — Manually closing a Spring-managed session breaks the transaction commit phase and throws Session is closed.
  • ❌ “Session wraps EntityManager using the Decorator design pattern.” — In Hibernate, Session inherits from EntityManager, and SessionImpl implements both directly.