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
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();
buildSessionFactory() is an expensive operation. It:
- Reads all mappings.
- Builds the metamodel.
- Validates relationships.
- Initializes the connection pool.
- Initializes caches.
- 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
SessionFactoryper request. This is a performance catastrophe: the entire metamodel and connection pool are rebuilt every time. - Holding multiple
SessionFactoryinstances 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.SessionFactoryis 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
What the Session does
- Provides the API for database operations:
save,persist,merge,delete,get,load,createQuery,flush. - Manages the Persistence Context: tracks all loaded entities, their changes, and dirty fields (dirty checking).
- Contains the First-Level Cache: every
get/loadchecks the cache first, then the database. - Manages the transaction: via
Transaction(orEntityTransactionin JPA). - 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
Sessionacross threads. It is not thread-safe and will corrupt the Persistence Context. - Keeping a
Sessionopen for too long. The first-level cache grows, leading to memory leaks and stale data. - Using
OpenSessionInViewfor 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 viaJOIN FETCHor@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
- Stores all managed (persistent) entities. Loaded a
User? It's here. - Guarantees identity. Two calls to
session.get(User.class, 1L)within the same session will return the exact same object instance (Identity Map pattern). - Dirty checking. Upon
flush, Hibernate compares the current state of entities with their snapshot taken at load time, and generates anUPDATEonly for the modified fields. - Write-behind.
save/persistdo not write to the DB immediately — they register the entity, andflushsynchronizes 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
}
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
Typical Workflow:
- App startup:
SessionFactoryis created (once). - User request:
Sessionis opened (for the duration of the request or transaction). - Work:
Sessionloads entities into the Persistence Context, tracking changes. - Flush: Upon commit or explicitly, changes are synchronized to the DB.
- Closure:
Sessionis closed, Persistence Context is destroyed. Entities become Detached.
Spring Specifics
In Spring Boot, things work slightly differently:
-
SessionFactory→ created by Spring as anEntityManagerFactory(usuallyLocalContainerEntityManagerFactoryBean). -
Session→ wrapped in anEntityManagerand bound to the current thread viaEntityManagerHolder. -
@Transactional→ manages the opening/closing of the session and transaction. -
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
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:
-
SessionFactory— one per application, heavyweight, thread-safe. Created at startup, lives until shutdown. -
Session— one per unit of work, lightweight, not thread-safe. Opened and closed per transaction. - Persistence Context — the first-level cache inside the
Session. Provides identity, dirty checking, and write-behind. - Never share a
Sessionacross threads. Ever. - Never create a
SessionFactoryper request. Ever. -
OpenSessionInViewis an anti-pattern. Disable it in production. - After a
Sessionis closed, entities become Detached. Attempting lazy loading will result in aLazyInitializationException.
Top comments (0)