Performance and Statistics

This section documents Hibernate ORM 7.4.x (User Guide, Introduction, Query Language Guide, Data Repositories Guide), Jakarta Persistence 3.2, Hibernate Search 8.4.x, and the Hibernate Validator / Hibernate Reactive references — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production.

Three older reference books were consulted as bibliography only while preparing these pages and are not the primary or main source for any page. All three predate Jakarta Persistence 3.2 and Hibernate ORM 6/7 (the javax.persistencejakarta.persistence namespace change, the ORM 6 query-engine rewrite, the Hibernate Search 6+ Elasticsearch backend), so the official documentation above wins on any discrepancy.

This section’s bibliography lists the reference material consulted while preparing these pages.

Most of Hibernate’s performance surface is already covered per-topic elsewhere in this section (Fetching & N+1, Bulk Operations & Batching, Second-Level Cache). This page is the checklist and the diagnostic tools that tell you which of those actually matters for a given application.

The performance checklist, distilled

  • Default every association to FetchType.LAZY; load eagerly per-query via JOIN FETCH/entity graphs only for what a specific use case needs.

  • Set spring.jpa.open-in-view=false (or the equivalent for a non-Spring bootstrap) and fetch exactly what a response needs inside the transactional service method.

  • Use SEQUENCE (not IDENTITY) generation where the database supports it, to keep JDBC batching intact.

  • Set hibernate.jdbc.batch_size and hibernate.order_inserts/order_updates for any workload doing more than a handful of writes per transaction.

  • Project read-only views as DTOs (SELECT new) instead of loading full managed entities.

  • Reach for StatelessSession or the flush()/clear() loop for large batch/ETL work.

  • Cache only what is actually re-read often and rarely changes; verify with the Statistics API rather than assuming a cache region is paying for itself (see Second-Level Cache's query-cache pitfall).

The Statistics API and hibernate.generate_statistics

Statistics stats = sessionFactory.getStatistics();
stats.setStatisticsEnabled(true); // also settable via hibernate.generate_statistics=true

long queries = stats.getQueryExecutionCount();
long slowest = stats.getQueryExecutionMaxTime();
String slowestQuery = stats.getQueryExecutionMaxTimeQueryString();

CacheRegionStatistics bookCache = stats.getCacheRegionStatistics("book");
double hitRatio = (double) bookCache.getHitCount()
        / (bookCache.getHitCount() + bookCache.getMissCount());

Statistics exposes per-entity, per-collection, per-query, and per-cache-region counters (load/fetch/update/ insert/delete counts, cache hit/miss/put counts, query execution counts and timings) — the direct way to confirm a suspected N+1 pattern, a cold cache region, or a slow query, rather than guessing from application behavior alone.

The slow-query log

hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS logs (at WARN) any single query whose execution exceeds the given threshold, including the SQL and its execution time — a low-overhead, always-on way to catch performance regressions in production without enabling full statistics collection or hibernate.show_sql.

Java Flight Recorder events

Hibernate ORM emits JFR (Java Flight Recorder) events for key persistence-context operations (session opens, flushes, JDBC batch execution, cache access) when running under a JDK with JFR enabled — lets Hibernate-level activity be correlated, in one recording, with the rest of the JVM’s own GC/allocation/lock-contention events already captured by JFR, without adding a separate Hibernate-specific profiling agent.

Connection-pool tuning

Hibernate’s own performance is frequently bottlenecked by the connection pool underneath it rather than anything Hibernate itself does: a pool sized too small serializes otherwise-parallel requests waiting for a connection; a pool sized too large can overwhelm the database’s own max-connections limit. HikariCP (the Spring Boot default) exposes maximum-pool-size, minimum-idle, connection-timeout, and leak-detection- threshold as the settings worth tuning first; see also Metrics & Observability for exposing pool metrics via Micrometer/Actuator.

Common anti-patterns

  • Open-session-in-view left on by default — hides N+1 problems in the serialization layer and holds a database connection for the whole request; see Fetching & N+1.

  • Eager fetching everywhere — loading associations that most call sites never use, on every load of the owning entity.

  • Entity-instead-of-DTO for read-only views — pays persistence-context and dirty-checking overhead for data that is never going to be written back.

  • @OneToMany with no @BatchSize — lazily-initialized collections default to one SELECT per parent, the textbook N+1 shape, unless batched.