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.
- Enable SQL logging and observe the actual generated SQL for a repository call you've only reasoned about abstractly before.
- Enable Hibernate statistics and query the exposed metrics after running a handful of requests.
- 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 →