JDBC Database Load Testing and Connection-Pool Considerations: Guided Hands-On Workflow
The guided workflow uses H2 server mode instead of an in-process database so the database is a separate local process and active sessions can be observed independently while JMeter runs. The mandatory workload remains SELECT-only.
Learning objectives
- Initialize a disposable H2 database and grant SELECT-only access to JMeter.
- Run H2 TCP server on localhost:9092 without remote-access flags.
- Install the H2 JDBC driver in JMeter and configure JDBC Connection Configuration.
- Run prepared SELECTs with typed parameters and safe row assertions.
- Observe JMeter pool behavior, active DB sessions, and H2 query statistics.
- Compare baseline per-thread pooling with a tiny shared-pool configuration without conflating it with application pooling.
1. Hard safety envelope
./db, server bound to loopback
127.0.0.1:9092, JMeter account
JMETER_READ with SELECT only, maximum 2 threads,
baseline 10 loops, comparison ≤10 seconds. Stop on unexpected
writes, non-loopback bind/URL, more than 3 JMETER_READ sessions,
repeated SQL errors, or generator saturation.
2. Obtain and verify the H2 driver
Download the current H2 2.5.250 all-platform
distribution from the official H2 site. Copy
h2-2.5.250.jar to a lab tools/ directory
and copy the same JAR into JMETER_HOME/lib. Restart
JMeter after adding/removing a JDBC driver.
Verify:
java -version
java -cp tools/h2-2.5.250.jar org.h2.tools.Shell -help
H2 2.5.250 requires Java 11+; this course uses Java 17.
3. Create synthetic rows and the SELECT-only account
Save fixtures/setup.sql:
DROP TABLE IF EXISTS PUBLIC.CATALOG_ITEM;
DROP USER IF EXISTS JMETER_READ;
CREATE TABLE PUBLIC.CATALOG_ITEM (
ID INTEGER PRIMARY KEY,
CATEGORY VARCHAR(32) NOT NULL,
PRICE_CENTS INTEGER NOT NULL,
DESCRIPTION VARCHAR(128) NOT NULL
);
INSERT INTO PUBLIC.CATALOG_ITEM (ID, CATEGORY, PRICE_CENTS, DESCRIPTION)
SELECT
X,
'CAT-' || MOD(X, 10),
1000 + MOD(X * 37, 9000),
'synthetic-item-' || X
FROM SYSTEM_RANGE(1, 5000);
CREATE INDEX IDX_CATALOG_CATEGORY ON PUBLIC.CATALOG_ITEM(CATEGORY);
CREATE USER JMETER_READ PASSWORD 'fake-read-pass';
GRANT SELECT ON PUBLIC.CATALOG_ITEM TO JMETER_READ;
SET QUERY_STATISTICS TRUE;
ALTER USER SA SET PASSWORD 'fake-admin-pass';
The setup script performs writes only while creating the disposable lab. JMeter itself never receives write permissions.
4. Initialize from a fresh lab directory
./db if you want a clean reset.
PowerShell:
New-Item -ItemType Directory -Force .\db, .\results | Out-Null
java -cp .\tools\h2-2.5.250.jar org.h2.tools.RunScript `
-url "jdbc:h2:./db/jmeterlab" `
-user sa `
-script .\fixtures\setup.sql
Bash:
mkdir -p db results
java -cp tools/h2-2.5.250.jar org.h2.tools.RunScript -url "jdbc:h2:./db/jmeterlab" -user sa -script fixtures/setup.sql
The script ends by changing SA's password to the synthetic
fake-admin-pass; subsequent monitoring uses that fake
admin credential only on localhost.
5. Start H2 TCP server on loopback
H2 documents -baseDir for restricting database files
and rejects remote hosts by default. Add an explicit bind address
system property:
PowerShell:
java -Dh2.bindAddress=127.0.0.1 `
-cp .\tools\h2-2.5.250.jar `
org.h2.tools.Server `
-tcp `
-tcpPort 9092 `
-baseDir .\db
Bash:
java -Dh2.bindAddress=127.0.0.1 -cp tools/h2-2.5.250.jar org.h2.tools.Server -tcp -tcpPort 9092 -baseDir ./db
-tcpAllowOthers. This
course never exposes the database server beyond loopback.
6. Preflight rows, permissions, and versions
Admin inspection:
java -cp .\tools\h2-2.5.250.jar org.h2.tools.Shell `
-url "jdbc:h2:tcp://127.0.0.1:9092/jmeterlab;IFEXISTS=TRUE" `
-user sa `
-password fake-admin-pass `
-sql "SELECT H2VERSION(); SELECT COUNT(*) AS ROWS FROM PUBLIC.CATALOG_ITEM; SELECT GRANTEE, RIGHTS, TABLE_NAME FROM INFORMATION_SCHEMA.RIGHTS WHERE GRANTEE='JMETER_READ';"
Expected: H2 2.5.250, 5,000 rows, and SELECT right on CATALOG_ITEM.
7. Prove the load credential can read
java -cp .\tools\h2-2.5.250.jar org.h2.tools.Shell `
-url "jdbc:h2:tcp://127.0.0.1:9092/jmeterlab;IFEXISTS=TRUE" `
-user JMETER_READ `
-password fake-read-pass `
-sql "SELECT ID, CATEGORY, PRICE_CENTS, DESCRIPTION FROM PUBLIC.CATALOG_ITEM WHERE ID=1;"
This confirms connectivity/permission without modifying data.
8. Configure JDBC Connection Configuration
| Field | Baseline value |
|---|---|
| Variable Name for created pool | labdb |
| Max Number of Connections |
0 — one private single-connection pool per
thread
|
| Max Wait | 1000 ms |
| Auto Commit | true |
| Transaction Isolation | TRANSACTION_READ_COMMITTED |
| Pool Prepared Statements | -1 (disabled for this beginner baseline) |
| Preinit Pool |
false; first-use connection establishment
remains visible
|
| Test While Idle | false |
| Validation Query | select 1 |
| Database URL |
jdbc:h2:tcp://127.0.0.1:9092/jmeterlab;IFEXISTS=TRUE
|
| JDBC Driver class | org.h2.Driver |
| Username | JMETER_READ |
| Password |
fake-read-pass — synthetic lab value only
|
The pool variable labdb is the lookup key JDBC Request
uses. If names do not match exactly, the sampler cannot borrow the
intended connection.
9. Parameterize item IDs
Create data/item-ids.csv:
item_id
1
37
101
509
999
1501
2401
3333
4096
4999
CSV Data Set Config: Recycle=true for the tiny read workload, Stop Thread=false, Sharing mode All Threads.
10. Prepared SELECT sampler
JDBC Request — Prepared Item Lookup:
| Setting | Value |
|---|---|
| Pool Variable | labdb |
| Query Type | Prepared Select Statement |
| SQL |
SELECT ID, CATEGORY, PRICE_CENTS, DESCRIPTION FROM
PUBLIC.CATALOG_ITEM WHERE ID = ?
|
| Parameter values | ${{item_id}} |
| Parameter types | INTEGER |
| Variable Names |
DB_ID,DB_CATEGORY,DB_PRICE,DB_DESCRIPTION
|
| Query timeout | 2 seconds |
JMeter creates DB_ID_# for row count and
DB_ID_1, DB_DESCRIPTION_1 for the first
row.
11. Add safe result assertions
Add Response Assertion on JMeter Variable
DB_ID_# equals 1. Add another on
DB_DESCRIPTION_1 containing
synthetic-item-. These checks prove a valid row was
returned without relying only on a JDBC success flag.
12. Recommended baseline tree
Test Plan
├── JDBC Connection Configuration — labdb
├── CSV Data Set Config — item-ids.csv
└── Thread Group — 2 threads × 10 loops max
└── JDBC Request — Prepared Item Lookup
├── Response Assertion DB_ID_# == 1
└── Response Assertion DB_DESCRIPTION_1 contains synthetic-item-
Use View Results Tree only for a 1-thread ×1-loop GUI check. Disable it before CLI comparison runs.
13. Observe active JMeter DB sessions separately
While the tiny test runs, a separate admin shell can query only the synthetic load user's sessions:
1..20 | ForEach-Object {
java -cp .\tools\h2-2.5.250.jar org.h2.tools.Shell `
-url "jdbc:h2:tcp://127.0.0.1:9092/jmeterlab;IFEXISTS=TRUE" `
-user sa -password fake-admin-pass `
-sql "SELECT USER_NAME, SESSION_STATE, COUNT(*) AS CONNECTIONS FROM INFORMATION_SCHEMA.SESSIONS WHERE USER_NAME='JMETER_READ' GROUP BY USER_NAME, SESSION_STATE;"
Start-Sleep -Milliseconds 250
}
The monitoring shell creates its own admin connection, but the query
filters only JMETER_READ.
14. Run the baseline in CLI mode
jmeter.bat -n `
-t plans\jdbc-read-baseline.jmx `
-l results\baseline\results.jtl `
-j results\baseline\jmeter.log `
-e -o results\baseline\report
Preserve JTL and matching jmeter.log. Record generator
CPU/memory and H2 process CPU/memory during the run.
15. Inspect query statistics after the run
java -cp .\tools\h2-2.5.250.jar org.h2.tools.Shell `
-url "jdbc:h2:tcp://127.0.0.1:9092/jmeterlab;IFEXISTS=TRUE" `
-user sa -password fake-admin-pass `
-sql "SELECT SQL_STATEMENT, EXECUTION_COUNT, MIN_EXECUTION_TIME, MAX_EXECUTION_TIME, AVERAGE_EXECUTION_TIME, CUMULATIVE_EXECUTION_TIME FROM INFORMATION_SCHEMA.QUERY_STATISTICS ORDER BY CUMULATIVE_EXECUTION_TIME DESC FETCH FIRST 10 ROWS ONLY;"
Compare H2 execution time with JTL total elapsed and JDBC latency. The figures use different measurement boundaries and should not be expected to match exactly.
17. Challenge
A team wants to benchmark an API whose application server has a 20-connection pool. Should you set JMeter JDBC pool to 20 and call the direct DB result an application-pool test?
No. Direct JDBC bypasses the application pool entirely. Use API/application load to test that pool. Use direct JDBC separately if the question is database/query behavior under a deliberately defined number of JMeter client connections.
Knowledge check
Why is H2 run in TCP server mode here?
It separates the database into its own process and makes active sessions/DB telemetry observable while JMeter runs.
Why is JMETER_READ safer than SA in JMeter?
It has SELECT permission only on the synthetic table, limiting accidental writes/destructive SQL.
Why use Prepared Select rather than embedding ${item_id} directly into SQL text?
Typed parameter binding avoids malformed/injected SQL and preserves driver-level prepared semantics.
What should roughly happen to DB sessions with max connections=0 and two active threads?
Each thread owns a one-connection pool, so up to roughly two JMETER_READ connections should be active.
Why compare JTL latency and H2 query statistics?
JTL latency exposes connection acquisition while H2 statistics measure database execution; their divergence helps identify pool wait versus query time.
Official references and version notes
- JMeter Component Reference — JDBC Connection Configuration — pool variable, max connections, max wait, validation, auto-commit, isolation, driver class, and credential storage.
- JMeter Component Reference — JDBC Request — prepared/select query types, parameters, result variables, query timeout, and connection-acquisition latency.
- JMeter Getting Started — Java requirements and GUI-versus-CLI execution.
- JMeter Dashboard Report — result-file requirements and post-run reporting.
- JMeter Best Practices — non-GUI load execution and generator validity.
- Apache JMeter downloads — current stable release and Java requirement.
- H2 Database Engine — current release and JDBC capabilities.
- H2 Quickstart — driver class and JDBC URL basics.
- H2 Tutorial — server mode, JDBC access, Shell, and Java tooling.
- H2 System Tables — SESSIONS and QUERY_STATISTICS telemetry.
- H2 Commands — CREATE USER, GRANT SELECT, SET QUERY_STATISTICS, and read-only SQL semantics.
Version-sensitive behavior was rechecked against current primary
documentation on 2026-09-05. The course baseline is
Apache JMeter 5.6.3 with a Java 17 JDK; JMeter
5.6.3 requires Java 8+. The mandatory database is
H2 2.5.250 (released 2026-08-29), whose current
engine requirements specify Java 11+ and which is tested with Java
11 and 17. The JDBC driver class is org.h2.Driver.
Copy the H2 driver JAR into JMETER_HOME/lib and
restart JMeter before authoring/running the JDBC plan. The H2 JAR
is a JDBC driver dependency, not a JMeter plugin. JMeter's JDBC
Connection Configuration uses Apache DBCP internally. Its
documented recommended
Max Number of Connections = 0 means each JMeter
thread gets its own one-connection pool; a positive value creates
a shared pool. JDBC Request latency is set to
connection-acquisition time, so pool wait can be separated from
total sample elapsed time. H2 server mode is bound to loopback
only in this chapter; no -tcpAllowOthers is used.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.