advanced~4h

Production Engineering — HikariCP, SQL Logging & Hibernate Statistics

Every technique from every earlier chapter is only verifiable in a real running application through the specific visibility tools this chapter covers — HikariCP tuning, SQL logging, and Hibernate statistics are your dashboard for an otherwise invisible persistence layer.

Learning objectives

  • Explain what HikariCP does and diagnose connection pool exhaustion back to its most common real root cause.
  • Use SQL logging to verify a fetching-strategy fix (JOIN FETCH, batch fetch) actually works as intended.
  • Enable and interpret Hibernate statistics for query counts and cache hit ratio in a production-monitoring context.

Every technique from every previous chapter — N+1 fixes, caching, locking — is only verifiable in a real, running application through the specific tools this chapter covers.

📖 Story

Imagine driving a car with no dashboard — no speedometer, no fuel gauge. You could still drive, but you'd have no idea you were about to run out of gas until it actually happened. Here's what that looks like for a Hibernate application:

# application.yml — with ZERO visibility configured spring: jpa: hibernate: ddl-auto: update # No show-sql, no statistics, no HikariCP tuning at all.

This runs — until a production incident hits, response times spike, and error logs show connection-pool-exhaustion errors, with absolutely no data to explain why.

Here's the same config, with the dashboard turned on:

spring: jpa: show-sql: true properties: hibernate: generate_statistics: true datasource: hikari: maximum-pool-size: 20 connection-timeout: 3000 # fail fast, don't hang forever

HikariCP — Spring Boot's default JDBC connection pool. Connection pool exhaustion — when every connection is in use and a new request has to wait or time out. Hibernate statistics — instrumentation exposing query counts and cache hit/miss ratios. SQL logging — logging the actual generated SQL for every statement.

Let's diagnose the exact incident from this chapter's opening story, now that the dashboard is turned on.

Why connections get exhausted — usually not "pool too small"

@Transactional public void processOrder(Order order) { orderRepository.save(order); externalShippingApi.call(order); // ⚠️ slow, non-database call INSIDE the transaction! }

This holds the database connection open for the ENTIRE duration of that slow external API call — under load, connections get tied up far longer than the actual database work needs, and the pool exhausts even if maximum-pool-size looks generous on paper. The fix isn't a bigger pool — it's moving the slow external call OUTSIDE the transactional boundary.

SQL logging — confirming a fix actually worked

Hibernate: select c1_0.id,c1_0.name from customers c1_0
Hibernate: select o1_0.customer_id,o1_0.id,o1_0.total from orders o1_0 where o1_0.customer_id=?
Hibernate: select o1_0.customer_id,o1_0.id,o1_0.total from orders o1_0 where o1_0.customer_id=?
... (48 more identical-shaped lines)

This log output is EXACTLY Chapter 12's N+1 problem, made visible — without show-sql, this would have stayed invisible until someone noticed the slowness in production.

Hibernate statistics — the same visibility, queryable at runtime

Statistics stats = entityManagerFactory.unwrap(SessionFactory.class).getStatistics(); System.out.println("Queries executed: " + stats.getQueryExecutionCount()); System.out.println("Second-level cache hit ratio: " + stats.getSecondLevelCacheHitCount());

HikariCP maintains an internal pool of physical JDBC connections; when your application needs one, HikariCP hands out an already-open connection rather than opening a new physical one — when the transaction completes, it's returned to the pool, not closed. Hibernate's statistics are collected via internal counters incremented at each relevant operation — a query execution, a cache hit — with small, generally negligible overhead.

  • This chapter's slow-external-API-inside-a-transaction example is one of the most common real root causes of connection pool exhaustion — traced back to connections held too long, not an undersized pool.
  • This chapter's SQL log output is the standard, first-response debugging step for 'why did this simple-looking code generate 500 SQL statements.'
  • Size HikariCP's pool based on actual measured concurrent load, not a guessed default.
  • Enable SQL logging in development/staging as standard practice; keep it available to toggle on temporarily in production for debugging.
  • Keep transactions short, exactly to avoid this chapter's slow-external-call trap.

⚠️ Why this keeps happening

Connection pooling and statistics are both invisible, working-correctly-by-default features until something goes wrong — most teams never configure HikariCP explicitly beyond the default, until a production incident forces a first, reactive look.

  • Diagnosing pool exhaustion by increasing pool size alone, without investigating WHY connections were held too long — exactly this chapter's external-API example, which a bigger pool wouldn't actually fix.
  • Never enabling SQL logging even temporarily, missing obvious N+1 patterns like this chapter's log-output example.
  • Leaving verbose SQL logging on in production permanently, adding unnecessary overhead to every request.

Correct HikariCP sizing directly affects throughput under load. SQL logging itself has real overhead — worth disabling for genuinely latency-critical, high-throughput paths while keeping it available to enable selectively.

Connection pool exhaustion triggered by a slow or abusive endpoint can function as an unintentional denial-of-service vector — a sane connection-timeout (failing fast, as in this chapter's config example) is a real defensive measure.

Dashboard HikariCP's active/idle/pending connection counts and Hibernate's query-count/cache-hit-ratio statistics as always-visible production metrics.

Set HikariCP's connection-timeout to fail fast (a few seconds, exactly this chapter's config) rather than queuing requests indefinitely.

  1. Enable SQL logging and observe the actual generated SQL for a repository call you've only reasoned about abstractly before.
  2. Enable Hibernate statistics and query the exposed metrics after running a handful of requests.
  3. Reproduce this chapter's slow-external-API-inside-a-transaction scenario, undersize the pool, and observe exhaustion — then fix it by moving the slow call outside the transaction.

✓ Quick recap

  • HikariCP pools reusable connections — sizing it for actual concurrent load matters directly for throughput.
  • Connection pool exhaustion is most often caused by connections held too long (this chapter's slow-external-call example), not simply an undersized pool.
  • SQL logging and Hibernate statistics make otherwise-invisible persistence-layer behavior visible — enable both rather than debugging blind.

Want a visual for this concept?

Generate a diagram tailored to “Production Engineering — HikariCP, SQL Logging & Hibernate Statistics” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.

Sign in to generate a visual →

Practice quiz

Next Step

Continue to Spring Boot Integration — Flyway/Liquibase, Auditing & Multi-Database Setups← Back to all Spring Data JPA & Hibernate Mastery chapters