Skip to main content
the lies your orm tells you

The Checklist: Hibernate Health Audit

5 min read Chapter 30 of 30

The Checklist: Hibernate Health Audit

This is not a chapter. It is a checklist. Print it. Run through it on your codebase. Fix what hurts. Ignore what does not.

Configuration

  • ddl-auto is set to validate or none in production
  • spring.jpa.open-in-view is set to false
  • JDBC batch size is configured (hibernate.jdbc.batch_size: 25-50)
  • hibernate.order_inserts and hibernate.order_updates are true
  • hibernate.query.in_clause_parameter_padding is true
  • Connection pool size follows Little’s Law, not a guessed number
  • hibernate.generate_statistics is false in production (enable only for debugging)
  • Second-level cache is enabled only for entities with measured cache hit rates above 80%
  • Query cache is disabled unless you have validated invalidation behavior

ID Generation

  • No GenerationType.IDENTITY on batch-inserted entities (blocks JDBC batching)
  • GenerationType.SEQUENCE uses allocationSize > 1 (default 50 is good)
  • No GenerationType.TABLE anywhere (row-level locking on the sequence table)

Fetch Strategies

  • Default fetch type for @OneToMany and @ManyToMany is LAZY (this is the JPA default, but verify)
  • Default fetch type for @ManyToOne and @OneToOne is LAZY (JPA defaults to EAGER for these, override explicitly)
  • No FetchType.EAGER on any association unless there is a measured reason
  • @BatchSize is set on frequently loaded lazy collections to prevent N+1
  • Fetch joins (JOIN FETCH) are used for known access patterns instead of relying on lazy loading

Queries

  • Repository methods for list/search endpoints return DTOs, not entities
  • No entity loading followed by mapping to DTOs (projection instead)
  • Aggregate queries (SUM, COUNT, AVG) run in the database, not in Java
  • No Hibernate.initialize() outside of test code (use fetch joins or entity graphs instead)
  • All read-only service methods are annotated with @Transactional(readOnly = true)

N+1 Detection

  • SQL logging is enabled in development (hibernate.show_sql or p6spy)
  • A test exists that asserts query count for critical endpoints (use datasource-proxy or Hibernate statistics)
  • No lazy collection access during JSON serialization (disabled OSIV should catch this)

Transactions

  • No @Transactional methods that call external HTTP services while holding a connection
  • Transaction boundaries are as narrow as possible (database work only)
  • @Transactional(readOnly = true) is used on all read-only paths
  • No @Transactional on controller methods (transactions belong in the service layer)
  • Optimistic locking retries use jittered exponential backoff, not immediate retry

Bulk Operations

  • Bulk inserts flush and clear the persistence context every N rows (batch size)
  • Bulk updates use JPQL UPDATE statements, not entity-by-entity modification
  • Bulk deletes use JPQL DELETE statements, not CascadeType.REMOVE on parent entities
  • Data imports of 10,000+ rows use StatelessSession or JDBC batch, not managed entities

Schema

  • All schema changes go through Flyway or Liquibase
  • No ghost columns in production (columns that exist in the database but not in entities)
  • Indexes exist for all foreign keys and frequently queried columns
  • Large table ALTER TABLE operations use CONCURRENTLY (PostgreSQL) or online DDL (MySQL)

Monitoring

  • Slow query log is enabled at the database level (not just Hibernate logging)
  • Connection pool metrics are exposed (HikariCP metrics via Micrometer)
  • Persistence context size is monitored for endpoints that load many entities
  • GC pause metrics are correlated with Hibernate-heavy endpoints

When to Skip Hibernate Entirely

Check these boxes if they apply. Three or more checked means you should consider jOOQ, JDBC Template, or another alternative for that code path.

  • The endpoint only reads data and never modifies it
  • The query requires window functions, CTEs, or recursive queries
  • The result set is larger than 10,000 rows
  • The endpoint is latency-sensitive (p99 < 50ms)
  • The code loads entities only to map them to DTOs immediately
  • The operation is a bulk import, export, or batch update
  • The team spends more time debugging Hibernate behavior than writing the query

This checklist covers the topics in this book. Not every item applies to every codebase. Start with the items that match symptoms you have already seen in production. Fix those first. Come back to the rest when you have time or when a new symptom appears.

The point of this book, and this checklist, is not to abandon Hibernate. Hibernate is a powerful tool for the problems it was designed to solve. The point is to recognize when you are outside those problems and to reach for the right tool instead of forcing the wrong one.