DEV Community

Silver_dev
Silver_dev

Posted on

Hibernate Under the Hood. Part 1: Hibernate Architecture and Key Components: SessionFactory and Session

Hibernate is an ORM framework that sits between your Java code and JDBC. To truly understand its behavior, you need a clear mental model of its architecture and the roles of two central objects: SessionFactory and Session. Everything else is derived from them.

Overall Architecture (Top to Bottom)

Application
    ↓
SessionFactory  (heavyweight, one per application)
    ↓
Session         (lightweight, one per unit of work)
    ↓
Persistence Context (first-level cache inside the Session)
    ↓
JDBC Connection / DataSource
    ↓
Database
Enter fullscreen mode Exit fullscreen mode

And on the side: Second-Level Cache, Connection Pool, Transaction Manager, Dialect, Interceptor/Listener — all of these reside at the SessionFactory level.

SessionFactory

The SessionFactory is an immutable, thread-safe, heavyweight factory that is created exactly once per application (per database). It holds:

  • Mapping metadata — all entities, their fields, relationships, and fetch strategies. Built once at startup.
  • Second-Level Cache (if enabled).
  • Connection settings — DataSource, connection pool, Dialect.
  • Compiled SQL queries derived from HQL/JPQL (query plan cache).
  • Event listeners — interceptors, callbacks.
  • Configurations for query cache, batching, and statistics.

How it is created:

In modern Spring Boot, you don't do this manually — Spring creates the SessionFactory (more accurately, the EntityManagerFactory) during context initialization. However, understanding what happens under the hood is crucial.

In "vanilla" Hibernate:

Configuration cfg = new Configuration()
    .addAnnotatedClass(User.class)
    .configure("hibernate.cfg.xml");

SessionFactory sessionFactory = cfg.buildSessionFactory();
Enter fullscreen mode Exit fullscreen mode

buildSessionFactory() is an expensive operation. It:

  1. Reads all mappings.
  2. Builds the metamodel.
  3. Validates relationships.
  4. Initializes the connection pool.
  5. Initializes caches.
  6. Compiles named queries.

At application startup, this takes seconds, and sometimes tens of seconds on large projects with thousands of entities.

Key Characteristics

Property Value
Thread-safety Yes
Lifespan Entire application (singleton)
Creation cost High
State Immutable after creation
Scope One per Application × Database
Spring equivalent LocalContainerEntityManagerFactoryBean

Typical Mistakes

  • Creating a SessionFactory per request. This is a performance catastrophe: the entire metamodel and connection pool are rebuilt every time.
  • Holding multiple SessionFactory instances without a valid reason. Usually, this is only needed for multi-tenancy or working with multiple databases.
  • Treating it as a "connection factory". No, connections are managed by the DataSource / Connection Pool. SessionFactory is a factory for sessions, not connections.

Session

The Session is a lightweight, short-lived, NON thread-safe object representing a single unit of work with the database. In JPA terminology, it corresponds to the EntityManager.

Inside the Session lives the Persistence Context — the first-level cache where all loaded and managed entities are stored.

Lifespan

A Session is created for:

  • A single transaction (transaction-scoped) — the most common scenario in Spring.
  • A single HTTP request (request-scoped) — via OpenSessionInViewFilter (a controversial practice, see below).
  • A single user conversation (long session, rare).

In Spring Boot, the Session is opened and closed automatically thanks to @Transactional:

@Transactional
public void updateUser(Long id, String name) {
    User user = session.get(User.class, id); // session is open
    user.setName(name);
} // session is closed, changes are flushed
Enter fullscreen mode Exit fullscreen mode

What the Session does

  1. Provides the API for database operations: save, persist, merge, delete, get, load, createQuery, flush.
  2. Manages the Persistence Context: tracks all loaded entities, their changes, and dirty fields (dirty checking).
  3. Contains the First-Level Cache: every get/load checks the cache first, then the database.
  4. Manages the transaction: via Transaction (or EntityTransaction in JPA).
  5. Performs flushing: synchronizes in-memory changes to the database.

Key Characteristics

Property Value
Thread-safety No (cannot be shared across threads)
Lifespan Single unit of work
Creation cost Low (lightweight object)
State Mutable
Scope One per Transaction / Request / Unit of work

Typical Mistakes

  • Sharing a Session across threads. It is not thread-safe and will corrupt the Persistence Context.
  • Keeping a Session open for too long. The first-level cache grows, leading to memory leaks and stale data.
  • Using OpenSessionInView for lazy loading in controllers. This is an anti-pattern: it causes N+1 queries, unpredictable behavior, and transaction issues. It is better to explicitly fetch required data via JOIN FETCH or @EntityGraph.

Persistence Context — The Heart of the Session

This is the most crucial concept to grasp. The Persistence Context is the first-level cache living inside the Session.

What it does

  1. Stores all managed (persistent) entities. Loaded a User? It's here.
  2. Guarantees identity. Two calls to session.get(User.class, 1L) within the same session will return the exact same object instance (Identity Map pattern).
  3. Dirty checking. Upon flush, Hibernate compares the current state of entities with their snapshot taken at load time, and generates an UPDATE only for the modified fields.
  4. Write-behind. save/persist do not write to the DB immediately — they register the entity, and flush synchronizes everything in batches.

Entity States

State Meaning How to get there
Transient New object, not associated with a session new User()
Persistent Managed by the session, inside the Persistence Context session.get(), session.persist(), session.merge()
Detached Was persistent, but the session is closed or the object was evicted After Session closure, session.evict()
Removed Marked for deletion session.remove()

Dirty Checking Example

@Transactional
public void doWork() {
    User u = session.get(User.class, 1L); // loaded, snapshot saved
    u.setName("Bob");                      // modified in memory
    // flush on commit will compare with snapshot and execute UPDATE
}
Enter fullscreen mode Exit fullscreen mode

No explicit session.update() is needed. This is the main magic of Hibernate.

How They Interact

Application → SessionFactory.openSession() → Session
                  ↑                              ↓
                  │                     Persistence Context (1st-level cache)
                  │                              ↓
            (created once)                 JDBC Connection (from pool)
                                                 ↓
                                          Transaction
Enter fullscreen mode Exit fullscreen mode

Typical Workflow:

  1. App startup: SessionFactory is created (once).
  2. User request: Session is opened (for the duration of the request or transaction).
  3. Work: Session loads entities into the Persistence Context, tracking changes.
  4. Flush: Upon commit or explicitly, changes are synchronized to the DB.
  5. Closure: Session is closed, Persistence Context is destroyed. Entities become Detached.

Spring Specifics

In Spring Boot, things work slightly differently:

  1. SessionFactory → created by Spring as an EntityManagerFactory (usually LocalContainerEntityManagerFactoryBean).
  2. Session → wrapped in an EntityManager and bound to the current thread via EntityManagerHolder.
  3. @Transactional → manages the opening/closing of the session and transaction.
  4. OpenEntityManagerInView → keeps the session open until the end of the HTTP request.

Useful Configuration Settings

# Logical? No — the physical connection is acquired from the pool
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true

# Batching — critical for performance
spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true

# Prevent Hibernate from modifying the DB at startup (use Flyway/Liquibase instead)
spring.jpa.hibernate.ddl-auto=validate

# Open session in view — do not use in production
spring.jpa.open-in-view=false
Enter fullscreen mode Exit fullscreen mode

Summary

Criterion SessionFactory Session
Weight Heavyweight Lightweight
Thread-safety Yes No
Lifespan Application Transaction / Request
Creation Expensive Cheap
State Immutable Mutable
Contains Metadata, 2nd-level cache, pool Persistence Context, 1st-level cache
Spring equivalent EntityManagerFactory EntityManager

Key Takeaways:

  1. SessionFactory — one per application, heavyweight, thread-safe. Created at startup, lives until shutdown.
  2. Session — one per unit of work, lightweight, not thread-safe. Opened and closed per transaction.
  3. Persistence Context — the first-level cache inside the Session. Provides identity, dirty checking, and write-behind.
  4. Never share a Session across threads. Ever.
  5. Never create a SessionFactory per request. Ever.
  6. OpenSessionInView is an anti-pattern. Disable it in production.
  7. After a Session is closed, entities become Detached. Attempting lazy loading will result in a LazyInitializationException.

Top comments (0)