Chapter 16Lesson 02~235 minutes

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.

H2 2.5.250SELECT-only userPrepared JDBCSessionsQuery statistics

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

Disposable/local only: database directory ./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

Destructive only to the disposable lab directory: stop H2 first, verify your current directory, then remove only ./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
Do not add -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.

16. Small shared-pool comparison

Duplicate the plan and change Max Number of Connections from 0 to 2. Now both threads use one shared JMeter pool that can hold two physical connections. Keep threads/query/data identical.

Do not interpret this as “application pool=2.” It is only a JMeter client configuration comparison.

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?

Why is JMETER_READ safer than SA in JMeter?

Why use Prepared Select rather than embedding ${item_id} directly into SQL text?

What should roughly happen to DB sessions with max connections=0 and two active threads?

Why compare JTL latency and H2 query statistics?

Next lesson

Choose direct DB design deliberately

Lesson 3 compares direct JDBC with application/API load, per-thread versus shared pools, read/write risk, prepared parameters, and embedded/server/container database models.

Official references and version notes

Version and compatibility note

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.