LearnThatStack Ace your next interview

Hibernate

Hibernate 7 with JPA. Older ways are marked. Checked Nov 2026 124 code snippets

The model

  • Hibernate is an ORM on top of JDBC. It maps objects to tables and writes the SQL. It implements JPA, now Jakarta Persistence, the standard API.
  • A SessionFactory is built once and shared by all threads. A Session is one unit of work. It isn't thread-safe, so each request gets its own.
  • The session's persistence context tracks every entity it loads. That's the first-level cache, so loading a row twice returns the same object.
  • At flush, dirty checking compares each tracked entity with its snapshot and writes the changes. You make no save call.
  • A lazy association loads on first use and needs an open session. N+1 queries and LazyInitializationException both start here.
Transientnew, no rowDetachedsession closedPersistentsession tracks itDatabaserowsENTERpersist()merge()find() snapshot takenLEAVEclose()WRITEflush INSERT or UPDATEremove + flush DELETE
Only a persistent entity is tracked. After remove(), it stays in the removed state until flush deletes its row.

Hibernate, JPA & setup

what Hibernate is and how it starts

Configure Hibernate

Use a file on the classpathhibernate.cfg.xmlhibernate.properties
Set it in code, or start through JPAnew Configuration()persistence.xml

TipHibernate detects the dialect from JDBC metadata, so that setting is usually optional.

JDBC, Hibernate or JPA

Control every statement by handJDBC. You write the SQL and map the results.
Map a large model of related entitiesHibernate. It writes the SQL and maps rows to objects.
Keep the option to switch providersUse only JPA, with EntityManager and JPQL.
Use filters, @BatchSize or custom typesUse Hibernate's own features. They tie you to it.

TipHibernate adds caching, and HQL that runs on any supported database. Session extends JPA's EntityManager.

Core types

Read settings and build the factoryConfiguration
Create sessions and hold the second-level cacheSessionFactory
Do one unit of work, on one threadSession
Begin, commit or roll backTransaction
Run a query, or build one in codeQueryCriteriaBuilder

TrapBuild the SessionFactory once at startup, because it's expensive. Open a Session per request or thread, and close it.

Sessions & entity states

what the session tracks and when it writes

Entity states

Detach entities by closing the sessionclose()
Delete the row at the next flushremove(user)

TipA new object is transient. A persistent object is also called managed, and the session saves its changes.

save, persist or saveOrUpdate

Save and get the id backsave(user)
Save a new object, and return nothingpersist(user)
Save or update, after checking the idsaveOrUpdate(user)

TipHibernate 7 removed save() and saveOrUpdate(). Use persist(), or merge() for a detached object.

TrapWith an identity column, save() inserts at once, even outside a transaction. persist() never does.

merge or update

Reattach the object you passupdate(user)
Copy a detached object onto a managed onemanaged = merge(user)

Tipupdate() throws if the session already holds that id. Hibernate 7 removed it.

Trapmerge() leaves your object detached. Keep using the one it returns.

get or load

Load the row now, or get nullfind(User.class, id)get(User.class, id)
Get a proxy, with no SELECT yetgetReference(User.class, id)
Do the same before Hibernate 7load(User.class, id)

TipA proxy sets a foreign key without loading the row, as in order.setUser(ref).

TrapA proxy fails later on a missing row. Its first use throws ObjectNotFoundException.

openSession or getCurrentSession

Get a new session, and close it yourselfopenSession()
Get the one bound to this contextgetCurrentSession()It needs an active transaction, and Hibernate closes it at the end.

Tiphibernate.current_session_context_class sets the context to thread or jta.

TrapNeither is thread-safe. A bound session is safe only because each thread or transaction gets its own.

Dirty checking

Save a change to a tracked entityuser.setName("New")No save call. Flush compares it with the snapshot.
Update only the changed columns@DynamicUpdateHibernate then builds the SQL at runtime.

TipBy default, the UPDATE sets every mapped column, so one cached statement serves all updates.

Flush modes

AUTOKeep queries correct, the default
COMMITRun faster, accept stale results
MANUALFlush only when you call flush()
ALWAYSFlush before every query
flushes before itdoesn't flush

TipAUTO flushes only before a query the changes could affect. Set a mode with setHibernateFlushMode().

TrapBy default, Hibernate doesn't flush before a native SQL query.

Sessions in a web app

Handle a web requestSession per request, with one transaction.
Span several requestsSession per conversation, with @Version.
Stop lazy queries in the web layerspring.jpa.open-in-view=false

TipAfter an exception, roll back and discard the session. It's no longer usable.

TrapNever share one session across the app. It isn't thread-safe, and its cache keeps growing.

Map classes to tables

annotations for entities, values and keys

Entity annotations

Map a class to a table@Entity@Table(name = "users")
Mark the primary key@Id@GeneratedValue
Rename a column, or leave a field out@Column(name = "x")@Transient

TipWithout @Table, the table takes the entity's name.

TrapAn entity needs an @Id and a no-argument constructor that isn't private. The class can't be final.

Value types and composite keys

Define a value type, and hold it in a field@Embeddable@Embedded
Rename columns to embed a type twice@AttributeOverride
Keep a composite key in one field@EmbeddedId
Keep each key column as its own @Id field@IdClass(RoleId.class)

TipA value type has no table and no id. Its fields become columns in the entity's table. @Embedded is optional.

TrapA key class must be Serializable, have a no-argument constructor, and override equals() and hashCode().

Inheritance strategies

Put the whole hierarchy in one tableSINGLE_TABLEThe default, usually fastest, with a discriminator column.
Normalize, with one table per classJOINEDLoading joins the tables, so queries are slower.
Give each concrete class a full tableTABLE_PER_CLASSA query on the parent needs a UNION.

TipChoose it with @Inheritance. One table can waste space on subclass columns.

Custom types

Convert a simple value@ConvertWrite a JPA AttributeConverter.
Control how JDBC reads and writes a value@Type(MyType.class)Implement UserType, or CompositeUserType for several columns.

TrapA UserType from Hibernate 5 doesn't compile on 6, and 7 changed nullSafeGet() and nullSafeSet() again.

Naming strategies

Change every name sent to the databasePhysicalNamingStrategy
Pick a name when no annotation gives oneImplicitNamingStrategy
Get snake case with no custom codePhysicalNamingStrategySnakeCaseImpl

TipRegister yours in hibernate.physical_naming_strategy.

equals and hashCode

Base them on an immutable business key@NaturalIdAdd a unique constraint too.
Compare types so a proxy still matcheso instanceof Book bA proxy is a subclass, so getClass() fails.

TrapA generated id is null until Hibernate persists the entity. A hash code built on it changes, and a HashSet loses the entity.

Associations & cascade

link entities and pass operations on to children

Association mappings

In User, the inverse side@OneToMany(mappedBy = "user")private List<Order> orders;In Order, the owning side@ManyToOne@JoinColumn(name = "user_id")private User user;
Map the other two kinds@OneToOne@ManyToMany

TrapIn a bidirectional link, Hibernate writes only changes made on the owning side. A change to the mappedBy side alone writes nothing.

Cascade types

Persist or merge children with the parentPERSISTMERGE
Delete children with the parentREMOVE
Reload or detach children tooREFRESHDETACH
Apply all of the aboveALL
Delete a child removed from the collectionorphanRemoval = true

TipSet them with CascadeType. Cascade fits a parent that owns its children.

TrapAvoid REMOVE and ALL on @ManyToMany. Removing one entity deletes rows that other entities still use.

Lazy loading & N+1

load what you use, in as few queries as you can

Lazy or eager

Load on first access, the to-many default@OneToMany@ManyToMany
Load with the parent, the to-one default@ManyToOne@OneToOne
Make a to-one association lazyfetch = FetchType.LAZY

TipMake almost every association lazy. Fetch what a use case needs with a join fetch or an entity graph.

Fix the N+1 problem

lazy loop@BatchSizesubselectjoin fetch

Getting the orders of 5 users. Each bar is one query, and the dark one loads the users. A lazy loop sends 6, a batch size of 2 sends 4, a fetch join 1.

Load users and orders in one queryLEFT JOIN FETCH u.orders
Load several users' orders per query@BatchSize(size = 10)
Load all collections with one extra query@Fetch(FetchMode.SUBSELECT)

TipA lazy association read in a loop causes it. A JPA entity graph also fixes it, by naming what to fetch.

Fix LazyInitializationException

Fetch it in the queryjoin fetch o.items
Load it inside the transactionHibernate.initialize(o.getItems())
Send only the fields the caller usesUse a DTO projection.

TipThe cause is code reading a lazy field after its session closed, often in a controller or a JSON serializer.

TrapEAGER and Open Session in View only hide it. EAGER loads on every query, and the view runs extra lazy queries.

HQL, Criteria & filters

ask for entities, not tables

HQL and native SQL

Query entities, not tablesFROM User u WHERE u.name = :n
Use a database-specific featurecreateNativeQuery(sql)
Page through resultssetFirstResult(20)setMaxResults(10)

TipHQL is a superset of JPQL. A query on a parent class also returns its subclasses.

TrapClass and property names are case-sensitive in HQL. Keywords such as select aren't.

Criteria or Query by Example

Add conditions at runtimegetCriteriaBuilder()It also does joins and grouping. The JPA metamodel makes it type-safe.
Match the fields set on an example objectExample.of(user)It can't nest or group conditions.
Use Hibernate's own Criteria APIorg.hibernate.criterionHibernate 6.0 removed it, with its Example class.

TrapExample.of() is from Spring Data JPA, not Hibernate.

Filters

Declare a filter and its parameters@FilterDef
Attach the SQL condition to the entity@Filter
Turn it on for this sessionenableFilter("active")

TipA new session starts with every filter off.

TrapBy default, filters don't apply to find() or to @ManyToOne associations. Set applyToLoadByKey (6.6+) to include them.

First & second-level cache

skip the database when the data hasn't changed

Cache levels

Share entities across sessions@Cache@Cacheable
Turn the second level onhibernate.cache.use_second_level_cache=true
Cache query resultshibernate.cache.use_query_cache=true

TipThe first level ends with its session. The second level suits data read often and changed rarely.

TrapThe query cache is rarely worth it. Any change to a queried table clears its results.

Second-level strategies and providers

Cache data that never changesREAD_ONLY
Cache with no locksNONSTRICT_READ_WRITE
Cache with soft locksREAD_WRITE
Use a fully transactional providerTRANSACTIONAL
Cache on a single nodeCaffeine, a fast local cache.
Share the cache across nodesHazelcast, Infinispan or a clustered Ehcache.

TipHibernate has no second-level cache of its own. Most providers plug in through JCache, with hibernate-jcache.

Transactions & locking

keep concurrent transactions correct

Optimistic locking

Catch conflicting updates without row locks@Version
Handle a conflict in the service layerOptimisticLockExceptionReload and retry, or show the conflict.
Lock the row when conflicts are frequentSELECT ... FOR UPDATEThis is pessimistic locking.

TipHibernate adds the old version to the WHERE clause. If another transaction changed the row, no row matches and Hibernate throws.

Isolation levels

READ_UNCOMMITTEDAllow all three read problems
READ_COMMITTEDStop dirty reads
REPEATABLE_READAlso stop non-repeatable reads
SERIALIZABLEAlso stop phantom reads
preventedcan happen

TipA dirty read sees uncommitted data, a non-repeatable read a new value, and a phantom read new rows. Set the level with @Transactional(isolation = ...).

TrapHigher levels hold more locks or abort more transactions, so throughput drops.

Bulk work & performance

load less, send fewer statements, measure first

Process large datasets

Send writes as JDBC batcheshibernate.jdbc.batch_size=50
Keep memory flat by clearing every 50 rowsflush()clear()
Load less for a read-only screenSelect a DTO projection, and page the results.

TrapHibernate can't batch inserts with IDENTITY ids. It must run each INSERT to learn the id, so use a sequence.

Bulk statements

Change many rows in one statementUPDATE User SET status = :sexecuteUpdate()
Delete many rows in one statementDELETE FROM User WHERE lastLogin < :d
Read rows through a cursorscroll(ScrollMode.FORWARD_ONLY)

TrapA bulk statement bypasses the persistence context and optimistic locking. Loaded entities keep their old values.

Stateless sessions

Open a session with no persistence contextopenStatelessSession()
Write a row at onceinsert(user)update(user)No dirty checking or cascades, and lazy data needs fetch().
Set its batch size (7+)setJdbcBatchSize(50)

TipIt uses little memory on any number of rows. Two reads of one row can return two objects.

TrapSince Hibernate 7, it uses the second-level cache by default, and hibernate.jdbc.batch_size doesn't apply to it.

Connection pool

Pick a production poolHikariCP, which Spring Boot prefers, or Agroal.
Cap the pool's connectionshibernate.hikari.maximumPoolSize=20

TipWhen every connection is busy, a request waits up to connectionTimeout, then fails.

TrapHibernate's built-in pool is for testing, not production.

Find slow queries

Print SQL while you debug locallyhibernate.show_sql=true
Log each statementlogging.level.org.hibernate.SQL=DEBUG
Log bound values (6+)logging.level.org.hibernate.orm.jdbc.bind=TRACE
Collect statistics from startuphibernate.generate_statistics=true

TipMost problems are too many queries, not one slow one. Count the statements per request first.

Schema, audit & services

what senior interviews add

Schema changes

Check the schema at startup, change nothingvalidate
Add what's missing, in developmentupdate
Recreate the tables at startup, or drop them at closecreatecreate-drop
Change a production schemaUse Flyway or Liquibase, with validate.

TipSet hibernate.hbm2ddl.auto to one of these values.

TrapNever use create or create-drop against production data. They drop the existing tables.

Audit with Envers

Record every change in an audit table@Audited
Leave a field out of the history@NotAudited
Query the revisionsAuditReaderFactory.get(session)
Use the audit support in ORM itself (7.4+)@org.hibernate.annotations.Audited

TrapEnvers records passwords unless they're @NotAudited.

Interceptors and event listeners

Use one object with a callback per operationInterceptor
Use one handler per lifecycle eventPreInsertEventListener

TipAn interceptor can fill audit columns. Its onPersist() returns true only when it changed the state array.

Multi-tenancy

Give each tenant its own databaseStrongest isolation, but the most databases to run.
Give each tenant its own schemaMultiTenantConnectionProvider
Share tables, with a tenant column (6+)@TenantId
Tell Hibernate the current tenantCurrentTenantIdentifierResolver

TrapNative SQL queries aren't filtered by tenant. Add the condition yourself.

Hibernate in microservices

Keep data inside one serviceOne database per service, and no other service reads it.
Change data across servicesUse events and a saga, with eventual consistency. A Hibernate transaction covers one database.
Send data between servicesSend DTOs, never entities.

TrapMany instances multiply the connections a database must hold. Size each pool for its service's load.

Found this useful? Pass it on.
Pro · $10/mo

This sheet stays free. With Pro, you can:

  • Every answer on this sheet. Read the full answer to all 42 questions it links, not just the 5 free ones.
  • My sheet. The cards you star and the ones you miss in Quiz me, from every sheet, on one page to quiz and print.
  • PDFs. Download this sheet and My sheet, laid out for A4, with every question a link to its answer.
  • The rest of Pro. Every answer in the question library, and 1,000 AI credits a month.