Performance, Caching & Locking
Avoiding N+1 Selects
// N+1 problem: 1 query for authors, then 1 MORE per author when
// the LAZY .getPosts() is accessed inside the loop
for (Author author : session.createQuery("FROM Author", Author.class).getResultList()) {
System.out.println(author.getPosts().size()); // separate query EVERY time
}
// Fix — JOIN FETCH pulls the association in the SAME query
List<Author> authors = session
.createQuery("FROM Author a JOIN FETCH a.posts", Author.class)
.getResultList();LazyInitializationException
Accessing a LAZY association after its owning Session has closed throws LazyInitializationException — the proxy needs an active session to fetch the real data on first access, and there isn't one. Common in layered apps where a service-layer transaction closes before a later view/serialization layer touches a lazy field. Fix: eagerly fetch what's needed before the session closes, or restructure so lazy access happens while the session is still open.
First-Level vs. Second-Level Cache
The first-level cache (persistence context) is per-Session, automatic, and scoped to that session's lifetime. The second-level cache is optional, configured explicitly (Ehcache, Infinispan), and shared across sessions/the whole SessionFactory — meaning it needs its own invalidation strategy since data can go stale relative to concurrent changes from other sessions. Enabling it is a real trade-off, not a free win, for frequently-changing or heavily-shared entities.
Optimistic Locking
@Entity
public class Account {
@Id
private Long id;
private BigDecimal balance;
@Version // checked at update time — if another transaction already
private Long version; // bumped this, the update fails instead of
} // silently overwriting the other change
try {
account.setBalance(account.getBalance().subtract(amount));
session.merge(account);
tx.commit();
} catch (OptimisticLockException e) {
// retry: reload the entity and reapply the change
}
// Pessimistic locking (SELECT ... FOR UPDATE) blocks other transactions
// upfront instead — better for HIGH-contention scenarios, worse for low.Schema Management
hibernate.hbm2ddl.auto (or Spring Boot's spring.jpa.hibernate.ddl-auto) controls automatic schema generation — update/create are convenient for local dev, but production schema changes should go through Flyway or Liquibase instead of relying on Hibernate's auto-DDL.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free