skip to content

Statistics & SQL Logging

Seeing what Hibernate really does: the Statistics API's query and cache counters, proper SQL logging, and slow-query thresholds. Interviewers respect candidates who assert query counts in tests instead of discovering N+1 in production.

part ofHibernateoverview, primer and where to startread it →
on this pageshow

questions

5

In a plain JPA/Hibernate application, how do you make Hibernate print the SQL statements it executes, and how do you see the actual parameter values instead of the ? placeholders?

level: juniorimportance: must knowfreq 62%

answer

  1. show_sql = System.out, no appender
  2. org.hibernate.SQL at DEBUG = real logger
  3. format_sql pretty, use_sql_comments names the source
  4. values: TRACE on orm.jdbc.bind (H6) / BasicBinder (H5)
  5. p6spy inlines params + timing

basics

~20 s

Set hibernate.show_sql=true to print to stdout, or better, set the org.hibernate.SQL logger to DEBUG so SQL goes through your logging framework. hibernate.format_sql pretty-prints it. Parameters stay as ? until you enable TRACE on Hibernate's bind-parameter logger.

solid answer

~50 s

There are two independent switches. `hibernate.show_sql=true` writes statements straight to `System.out` — no logger, no level, no appender — so it is a local-dev toy. The real mechanism is the **`org.hibernate.SQL` logger at DEBUG**: the same statements, but routed through SLF4J/Logback/Log4j2 so you can enable them per environment and ship them to a log aggregator. `hibernate.format_sql=true` pretty-prints multi-line SQL, and `hibernate.use_sql_comments=true` prefixes each statement with a comment naming the HQL or entity operation that produced it. Neither shows bound values, because Hibernate uses `PreparedStatement` — the log shows `?`. For values you raise the bind logger to TRACE: `org.hibernate.orm.jdbc.bind` in Hibernate 6+, `org.hibernate.type.descriptor.sql.BasicBinder` in Hibernate 5. That is very chatty and puts personal data in logs, so treat it as a debugging switch. If you want one copy-pasteable statement with values inlined, use a JDBC proxy such as p6spy or datasource-proxy instead.

code

properties · 6 lines
properties
hibernate.format_sql=true
hibernate.use_sql_comments=true

# logback / log4j2 categories
logger.org.hibernate.SQL=DEBUG
logger.org.hibernate.orm.jdbc.bind=TRACE

go deeper

for a junior

Know both switches by name, know that the logger version is the one to prefer, and know that parameter values need a separate TRACE category.

for a middle

Explain why PreparedStatement placeholders exist, name the Hibernate 6 versus 5 bind categories, and mention use_sql_comments for tracing a statement back to its source.

for a senior

Frame it as an observability decision: what is safe to leave on, the privacy cost of logging bind values, and when to move to a JDBC proxy or database-side statement logging instead.

for a principal

Discuss the cost model of always-on statement logging at production volume and set an organisational default: comments on, SQL off, slow statements captured, values never logged.

## Why you cannot just read the SQL in your code Hibernate does not execute SQL you wrote — it generates it. Entity state changes become INSERT/UPDATE/DELETE at flush time, and HQL/JPQL/Criteria queries become SELECTs whose joins, aliases and column lists come from the mapping. So the first diagnostic step for almost any ORM problem is: make the generated statements visible. ## Switch 1: hibernate.show_sql `hibernate.show_sql=true` is the oldest switch. It makes Hibernate print each statement directly to `System.out`, bypassing the logging framework entirely. Consequences: you cannot set a level, cannot filter it, cannot attach it to a file appender, cannot correlate it with the rest of your log because it has no timestamp, thread name or MDC context. In a container it goes to the console stream where it may be interleaved unpredictably with real log output. It is convenient in a scratch project and inappropriate anywhere else. ## Switch 2: the org.hibernate.SQL logger Hibernate logs every statement it prepares to a category named exactly `org.hibernate.SQL`, at DEBUG level. Setting that category to DEBUG in logback.xml/log4j2.xml produces the same content as `show_sql`, but as ordinary log events: timestamped, thread-labelled, filterable, and routable. This is the version you want, because you can enable it for one test class, one environment, or one short window in staging without redeploying a code change. ## Formatting helpers - `hibernate.format_sql=true` breaks the statement across lines with indentation. Very useful for wide multi-join selects, noisy for one-line inserts. - `hibernate.highlight_sql=true` (Hibernate 6) adds ANSI colour for a terminal. - `hibernate.use_sql_comments=true` emits a leading `/* ... */` comment identifying the source: the HQL string, or something like `load com.example.Order`. This is the cheapest way to answer 'which line of code produced this query?' and it also shows up in the database's own statement log, which makes it valuable well beyond development. ## Bind parameters Hibernate binds values through `PreparedStatement`, which is what keeps it safe from SQL injection and lets the database cache plans. That is why the statement text contains `?`. The values are logged separately, by the type descriptors, at TRACE: - Hibernate 6 and later: `org.hibernate.orm.jdbc.bind` (and `org.hibernate.orm.jdbc.extract` for values read back out of the result set). - Hibernate 5: `org.hibernate.type.descriptor.sql.BasicBinder` (or the broader `org.hibernate.type`). These produce one line per parameter, so a batch of a thousand inserts becomes thousands of lines. They also dump the raw values — emails, names, tokens — into your log files, which is a privacy and compliance problem. Enable them narrowly and never by default in production. ## When logging alone is not enough The SQL log tells you what was sent, not how long it took, how many times, or whether a cache satisfied the request instead. It also does not stitch statement and parameters back into one runnable line. Tools that sit between Hibernate and the driver solve that: - **p6spy** wraps the `DataSource`/driver and logs a single line per statement with values inlined and the elapsed time. - **datasource-proxy** does the same programmatically and additionally exposes query counts, which makes it the usual basis for automated 'this endpoint must issue at most N statements' assertions. Both see everything that reaches JDBC, including statements from code that does not go through Hibernate at all. ## Production posture Running `org.hibernate.SQL` at DEBUG in production is a real cost: one log event per statement, at hundreds or thousands of statements per second, with the serialization and I/O that implies, plus log-storage bills. The normal setup is: SQL logging off by default, `use_sql_comments` on (cheap, and it makes the database-side slow-query log self-explanatory), and slow statements captured either by the database or by Hibernate's own slow-query threshold. Turn DEBUG on deliberately, briefly, and preferably on one instance.

  • Why does the logged statement contain ? instead of the value, and would inlining the value be an improvement?
    Hibernate uses JDBC PreparedStatement, so the SQL text and the parameter values travel separately; the driver never builds one concatenated string. That is what prevents SQL injection and lets the database reuse the execution plan across calls. Inlining is only a logging convenience — tools like p6spy reconstruct the readable statement for display, they do not change what is actually sent to the database.
  • You enabled DEBUG on org.hibernate.SQL and see the SELECT, but the returned entity has a null field. Does the log prove Hibernate read null from the database?
    No. The statement log shows what was sent, not what came back. Use the result-extraction logger (org.hibernate.orm.jdbc.extract at TRACE in Hibernate 6) to see the values actually read from the ResultSet, or a JDBC proxy that captures results. A null could equally come from a mapping mistake, a second query overwriting state, or reading a different instance from the persistence context.

saying these in an interview costs you the question

  • Believing show_sql and the org.hibernate.SQL logger are the same thing, or that show_sql respects log levels and appenders
  • Claiming format_sql makes bind parameter values appear
  • Leaving bind-parameter TRACE logging on in production, dumping user data into log files
  • Assuming the absence of a statement in the log means no database access, when a cache or an already-managed entity satisfied the call
  • Saying Hibernate concatenates parameter values into the SQL string it sends

context

open as a page

What does enabling the hibernate.generate_statistics setting give you, and what kinds of numbers can you read out of Hibernate's Statistics API?

level: middleimportance: should knowfreq 45%

basics

~20 s

It makes Hibernate count its own work into a Statistics object reachable from the SessionFactory: statements prepared, entities loaded/inserted/updated/deleted, queries executed with their times, collection and cache hit/miss/put counts, connections and flushes. Counters are cumulative until you clear them.

open as a page

Your team keeps re-introducing code paths that fire one SQL statement per row, and it is only noticed in production. How would you turn the number of SQL statements a code path executes into an automatically enforced, failing assertion in the test suite?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Count statements around the code and assert on the count. Either clear Hibernate's Statistics and assert getPrepareStatementCount(), or wrap the DataSource with datasource-proxy/p6spy and assert its captured query count. Run the assertion in a single-threaded test against a real database dialect.

open as a page

Hibernate can log a warning for every query slower than a configured number of milliseconds. Which setting turns that on, what exactly does the reported time measure, and how does it differ from the database server's own slow-query log?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

Set hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS to a millisecond threshold. Hibernate then logs each slower statement to the org.hibernate.SQL_SLOW category with its elapsed time, measured client-side around JDBC execution — so it includes network and server queueing, unlike the database's own log.

open as a page

For a long-running application using Hibernate, which ORM-level metrics would you actually export to a monitoring system, which would you deliberately leave off, and what does the collection itself cost?

level: principalimportance: nice to knowfreq 24%

basics

~20 s

Export monotonic counters — statements prepared, entity loads/inserts/updates, flushes, transactions and rollbacks, sessions opened versus closed, cache hits/misses/puts per region — and let the backend rate them. Leave off stateful maxima, per-statement logs, and bound parameter values.

open as a page