Spring Data JPA Pagination Queries and HikariCP Connection Pool Utilization in JHipster
0 reputation · 21 Jan 2026, 08:51 UTC
0 reputation · 21 Jan 2026, 08:51 UTC
When using Spring Data JPA's Pageable interface in a JHipster-generated service, each pagination request triggers two SQL statements: a data query with LIMIT/OFFSET and a separate count query to determine the total number of rows.
Under concurrent load, the count query can occupy a connection for the duration of its execution, especially on large tables lacking appropriate indexes, while the data query may also hold a connection if the transaction remains open waiting for downstream calls.
Given that HikariCP's maximum‑pool‑size is fixed in application.yml and that different SQL dialects generate slightly different count‑query plans, it is unclear whether the observed intermittent connection‑pool exhaustion stems primarily from the count query, the data query, or a combination of both.
What proportion of active connections is attributable to the count query versus the data query during peak pagination traffic?
Does adding a covering index for the count query significantly reduce connection hold time?
Should the service layer explicitly separate the count and data queries into different transactions to limit connection usage?
35275 reputation · 21 Jan 2026, 15:20 UTC
There is no fixed proportion of connections attributable to the count query versus the data query — it depends entirely on which query is slower in your schema. But in the scenario you describe (large tables, missing indexes, concurrent load), the count query is almost always the dominant connection holder, because a SELECT COUNT(*) that degenerates into a sequential scan can hold a connection orders of magnitude longer than an indexed LIMIT/OFFSET data query. Pool exhaustion happens when the arrival rate of requests outpaces the release rate of connections, and the slowest statement sets that release rate.
Page execution runs two statements: the data query and a derived count query. This is standard behavior when you return Page<T> from a repository.@Transactional service method, both statements execute on the same connection, held for the entire transaction — including any downstream calls made before commit. So the real question is often not "count vs. data" but "how long is the transaction open?"maximum-pool-size, any statement whose latency grows with table size becomes the bottleneck under concurrency.Before changing anything, get per-statement timing. Enable SQL logging temporarily in a staging load test:
logging.level.org.hibernate.SQL: DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder: TRACEBetter, expose HikariCP metrics through Micrometer (hikaricp.connections.active, hikaricp.connections.pending) and correlate spikes in pending threads with slow-query log entries from the database. Run the generated count query manually:
EXPLAIN ANALYZE SELECT COUNT(*) FROM your_table WHERE ...;If you see a sequential scan on a large table, you have found your connection hog. If the count is fast but connections still pile up, look at transaction duration instead — the data query's connection is being held open by code after the query, not by the query itself.
Usually yes, significantly, when the count query has a selective WHERE clause: an index whose columns satisfy the filter lets the database count from the index without touching the heap, collapsing hold time from seconds to milliseconds. Two caveats: a bare COUNT(*) over the whole table with no filter often still scans (PostgreSQL may use an index-only scan on the smallest index; MySQL/InnoDB behavior differs), and dialect-specific plans mean you must verify with EXPLAIN ANALYZE on your database rather than assume. This is the point most worth verifying in your environment before recommending anything else.
Generally no. Two transactions mean two connection acquisitions per request, which increases total pool pressure and adds latency; it also opens a consistency window where the total and the page contents disagree. The only defensible split is making the whole read path @Transactional(readOnly = true) so nothing downstream extends a writable transaction, and ensuring no remote calls happen inside it. Better options, in order of impact:
Slice<T> instead of Page<T>; Spring Data then skips the count query entirely.maximum-pool-size as a last resort — it shifts contention to the database server rather than removing it.If your EXPLAIN ANALYZE shows the count query is already fast (single-digit milliseconds) yet the pool still exhausts, the cause is transaction scope, not the count query — share the slow-query log or a HikariCP metrics snapshot and the recommendation shifts to transaction-boundary refactoring. Version note: this reflects long-standing Spring Data JPA and HikariCP behavior; exact count-query generation varies slightly across Spring Data releases, so confirm against your JHipster-generated dependencies.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.