Deepen PracticeOrdered learning track

JDBC, JTA, Resources, and REST Runtime Coupling

Learn Java Eclipse Jersey & GlassFish - Part 022

JDBC resources, connection pools, JNDI, JTA transaction boundary, resource lifecycle, timeout, leak, dan failure propagation dari database ke kontrak REST di GlassFish/Jersey.

20 min read3849 words
PrevNext
Lesson 2234 lesson track1928 Deepen Practice
#java#jakarta-ee#jersey#glassfish+11 more

Part 022 — JDBC, JTA, Resources, and REST Runtime Coupling

Target utama bagian ini: memahami bagaimana request REST di Jersey berinteraksi dengan JDBC resource, connection pool, dan JTA transaction di GlassFish, sehingga kita bisa mencegah pool exhaustion, transaction leak, timeout chaos, dan error contract yang tidak defensible.

Kita sudah punya seri khusus learn-java-sql-jdbc dan learn-java-persistence. Jadi bagian ini tidak mengulang SQL, JDBC API dasar, atau ORM mapping. Fokus kita lebih sempit dan lebih production-oriented:

What happens when an HTTP request consumes a database resource inside a Jakarta EE application server?

Di production, bug REST + database jarang hanya tentang query salah. Sering kali masalahnya adalah coupling runtime:

  • HTTP thread menunggu JDBC connection;
  • JDBC pool lebih kecil dari traffic concurrency;
  • transaction aktif terlalu lama karena serialization/remote call;
  • connection tidak ditutup;
  • JTA timeout tidak selaras dengan proxy timeout;
  • retry membuat duplicate write;
  • database lambat membuat semua endpoint ikut lambat;
  • health check terlalu agresif memukul database;
  • app jalan lokal karena embedded datasource, tetapi gagal di GlassFish karena JNDI resource tidak ada.

1. Kaufman Deconstruction

Skill ini kita pecah menjadi beberapa sub-skill.

Sub-skillYang Harus DikuasaiOutput Praktis
Resource modelJDBC driver, pool, resource, JNDIBisa membaca dependency runtime app
Connection lifecycleborrow/use/return/validationBisa mencegah leak dan pool exhaustion
Transaction boundarylocal tx, JTA, CDI @Transactional, UserTransactionBisa menentukan atomicity dengan jelas
Request couplingHTTP thread ↔ DB connection ↔ transactionBisa memprediksi bottleneck
Timeout alignmentHTTP, proxy, pool wait, query, transactionFailure lebih cepat dan jelas
Pool sizingconcurrency, latency, DB capacityTidak tuning secara mistik
Failure mappingSQL/JTA/pool error → REST error contractClient mendapat response stabil
Observabilitypool metrics, slow query, thread dump, logsRoot cause tidak ditebak
Deployment disciplineresource creation before deployArtifact portable antar environment

Mental model:

Prinsip inti:

Database access in a REST request is a shared runtime resource consumption problem, not just a DAO coding problem.


2. GlassFish JDBC Resource Model

Di GlassFish, aplikasi idealnya tidak membuat DriverManager.getConnection(...) sendiri. Aplikasi memakai DataSource yang disediakan container.

Modelnya:

Komponen:

ComponentPeranContoh
JDBC driverImplementasi vendor DBPostgreSQL/MySQL/Oracle driver jar
Connection poolMengelola physical/logical connectionordersPool
JDBC resourceJNDI alias yang dipakai aplikasijdbc/orders
Application lookupInjection/lookup ke resource@Resource(lookup="jdbc/orders")
ConnectionHandle yang dipinjam dari pooldataSource.getConnection()

Rule:

Application code should depend on a stable JNDI resource name, not environment-specific database URL/credential.


3. Why Container-Managed DataSource Matters

Container-managed datasource memberi:

  • pooling;
  • validation;
  • lifecycle management;
  • central configuration;
  • credential separation;
  • transaction integration;
  • monitoring;
  • deployment portability;
  • admin control.

Anti-pattern:

Connection c = DriverManager.getConnection(
    System.getenv("DB_URL"),
    System.getenv("DB_USER"),
    System.getenv("DB_PASSWORD")
);

Ini melewati:

  • GlassFish pool;
  • JTA enlistment;
  • monitoring pool;
  • resource targeting;
  • admin-managed rotation;
  • environment abstraction.

Untuk small standalone service, direct datasource mungkin masuk akal. Untuk GlassFish/Jakarta EE runtime, container-managed datasource adalah default yang lebih sesuai.


4. Injection Pattern

Contoh resource injection:

@RequestScoped
public class OrderRepository {

    @Resource(lookup = "jdbc/orders")
    private DataSource dataSource;

    public Optional<OrderRecord> find(UUID id) throws SQLException {
        String sql = "select id, status, amount from orders where id = ?";

        try (Connection con = dataSource.getConnection();
             PreparedStatement ps = con.prepareStatement(sql)) {
            ps.setObject(1, id);
            try (ResultSet rs = ps.executeQuery()) {
                if (!rs.next()) {
                    return Optional.empty();
                }
                return Optional.of(map(rs));
            }
        }
    }
}

Catatan penting:

  • DataSource boleh disimpan sebagai dependency;
  • Connection tidak boleh disimpan lintas request;
  • PreparedStatement dan ResultSet harus ditutup;
  • gunakan try-with-resources;
  • jangan return ResultSet keluar repository;
  • jangan membuka connection di filter lalu dipakai diam-diam oleh service;
  • jangan membuat transaction boundary implicit tanpa dokumentasi.

5. Connection Lifecycle

Lifecycle ideal:

Connection.close() pada pooled datasource biasanya berarti mengembalikan connection ke pool, bukan menutup physical socket ke DB setiap kali.

Invariants:

  • borrow as late as possible;
  • return as early as possible;
  • do not hold connection during CPU-heavy serialization;
  • do not hold connection during remote HTTP call unless truly required;
  • never store connection in static field;
  • never share connection across concurrent threads;
  • transaction boundary determines whether connection can be returned immediately or after transaction completion.

6. HTTP Thread ↔ JDBC Pool Coupling

Misalkan:

HTTP worker capacity: 200 concurrent requests
JDBC pool max:        30 connections
Average DB time:      100ms normal, 3s incident

Saat normal, 30 connections mungkin cukup. Saat DB melambat, 30 connections penuh, 170 HTTP threads bisa menunggu pool atau dependency lain. Akibatnya endpoint yang tidak memakai DB pun bisa ikut lambat jika pool/thread global habis.

Rule:

A slow database can become an HTTP thread starvation incident if pool waits are not bounded.


7. Pool Sizing Mental Model

Pool sizing bukan “semakin besar semakin baik”. Pool size harus mempertimbangkan:

  • database capacity;
  • query latency;
  • number of app instances;
  • transaction duration;
  • connection wait timeout;
  • endpoint mix;
  • burst behavior;
  • DB lock contention;
  • failover/reconnect behavior.

Jika ada 6 application instances dan setiap pool max=50, database bisa menerima 300 connection dari satu service. Ini bisa terlalu besar.

Total possible DB connections = app instances × pool max size

Kalau database hanya aman menerima 120 connection untuk workload tersebut, maka pool per instance harus dihitung, bukan ditebak.

7.1 Starting Formula

Gunakan Little's Law sebagai intuisi:

required concurrent DB connections ≈ DB-bound throughput × DB time

Contoh:

Target DB-bound throughput: 100 req/s
Average DB hold time:      80ms = 0.08s
Connections needed:        100 × 0.08 = 8

Tambahkan headroom, bukan 10x tanpa alasan.

Saat DB hold time naik ke 1s:

Connections needed: 100 × 1 = 100

Kalau pool hanya 20, sistem harus memilih:

  • tunggu;
  • reject cepat;
  • degrade;
  • shed load;
  • circuit break;
  • reduce traffic.

Jangan berharap pool kecil memproses unlimited burst tanpa queue impact.


8. Pool Settings That Matter

Nama option bisa berbeda antar versi/driver, tetapi konsepnya stabil.

SettingMaknaFailure Jika Salah
steady/min pool sizeconnection minimum yang dijagastartup spike atau resource idle terlalu besar
max pool sizebatas connection aktifpool exhaustion atau DB overload
max wait timewaktu menunggu connectionHTTP thread menggantung terlalu lama
idle timeoutkapan idle connection dilepasconnection churn atau idle resource tinggi
validation methodcara memastikan connection sehatstale connection lolos atau overhead tinggi
leak timeoutmendeteksi connection tidak dikembalikanleak tidak terlihat
statement timeoutbatas queryquery runaway
transaction timeoutbatas unit of worktx terlalu lama menahan lock

Rule:

Pool wait timeout must be shorter than the user-facing request timeout.

Kalau client timeout 5 detik tetapi pool wait 60 detik, server akan terus menahan thread untuk request yang client-nya sudah pergi.


9. Resource Creation with asadmin

Contoh konseptual untuk PostgreSQL-like datasource. Sesuaikan driver class, property, credential, dan target dengan environment.

# Driver jar placement must be handled before pool creation.
# Example: copy driver jar to a controlled server lib location and restart if required by your setup.

asadmin create-jdbc-connection-pool \
  --restype javax.sql.DataSource \
  --datasourceclassname org.postgresql.ds.PGSimpleDataSource \
  --property serverName=db.internal:portNumber=5432:databaseName=orders:user=${DB_USER}:password=${DB_PASSWORD} \
  ordersPool

asadmin create-jdbc-resource \
  --connectionpoolid ordersPool \
  jdbc/orders

asadmin ping-connection-pool ordersPool

Catatan:

  • javax.sql.DataSource tetap package Java SE, bukan namespace Jakarta EE;
  • jangan commit password literal ke repository;
  • target resource harus sesuai target deployment;
  • ping pool bukan bukti query bisnis benar;
  • resource harus ada sebelum deploy atau sebelum endpoint dipanggil;
  • driver visibility harus sesuai classloader/server lib strategy.

10. JNDI Name Discipline

JNDI name adalah contract antara application artifact dan runtime environment.

Good:

jdbc/orders
jdbc/audit
jdbc/reporting

Bad:

jdbc/prod-postgres-10-0-1-15-main-password-v2

JNDI name harus stabil dan domain-oriented. Detail environment ada di GlassFish config, bukan di code.

10.1 Naming Rule

Name TypeExampleQuality
Domain resourcejdbc/ordersGood
Capabilityjdbc/reporting-readonlyGood
Environment-specificjdbc/prod-db01Weak
Credential-specificjdbc/orders-user-adminDangerous
Vendor-specificjdbc/postgresOrdersSometimes acceptable, but leaks implementation

11. Transaction Boundary Options

Ada beberapa pola transaction boundary.

11.1 Local Transaction Manual JDBC

try (Connection con = dataSource.getConnection()) {
    con.setAutoCommit(false);
    try {
        // multiple SQL statements
        con.commit();
    } catch (Exception e) {
        con.rollback();
        throw e;
    }
}

Kelebihan:

  • eksplisit;
  • mudah dipahami untuk single datasource;
  • tidak butuh JTA untuk use case sederhana.

Risiko:

  • mudah lupa rollback;
  • sulit compose lintas repository;
  • tidak otomatis enlist resource lain;
  • mixed dengan container transaction bisa kacau;
  • exception mapping harus hati-hati.

11.2 CDI/Jakarta Transactional Boundary

@RequestScoped
public class OrderService {

    @Transactional
    public OrderDto approve(UUID orderId, ApproveCommand command) {
        // load order
        // mutate state
        // persist events/outbox
        // return DTO
    }
}

Kelebihan:

  • boundary di use case/service layer;
  • rollback policy lebih deklaratif;
  • lebih cocok untuk domain operation;
  • dapat dikelola container.

Risiko:

  • self-invocation/proxy issue;
  • transaction terlalu lebar jika service melakukan remote call;
  • rollback rule harus dipahami;
  • exception swallowing bisa membuat commit tidak sengaja;
  • streaming response tidak cocok dengan transaction panjang.

11.3 UserTransaction

@Resource
private UserTransaction tx;

public void run() throws Exception {
    tx.begin();
    try {
        // work
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
        throw e;
    }
}

Gunakan saat perlu explicit programmatic boundary, bukan sebagai default semua use case.


12. Where Should Transaction Start?

Idealnya transaction dimulai di application service/use-case boundary, bukan di Jersey resource method dan bukan di DAO paling bawah.

Resource method bertugas:

  • menerima HTTP contract;
  • mapping input;
  • memanggil use case;
  • mapping result/error ke response.

Service method bertugas:

  • enforce domain invariant;
  • menentukan transaction atomicity;
  • mengoordinasikan repository;
  • memutuskan side effect.

Repository bertugas:

  • operasi persistence spesifik;
  • tidak menentukan transaction use case secara global.

13. Transaction Scope Anti-Patterns

13.1 Transaction Around Remote Call

@Transactional
public void approve(UUID id) {
    orderRepository.markApproved(id);
    paymentClient.capture(...); // remote call inside transaction
    auditRepository.insert(...);
}

Risiko:

  • DB lock ditahan sambil menunggu network;
  • timeout remote menyebabkan transaction lama;
  • retry payment bisa duplicate;
  • transaction rollback tidak membatalkan remote side effect.

Alternatif:

  • outbox pattern;
  • state machine with pending external action;
  • saga/compensation;
  • idempotency key;
  • transaction hanya untuk local state transition.

13.2 Transaction Around Serialization

Jangan tahan transaction sampai JSON serialization selesai. Resource method harus mengembalikan DTO yang sudah detached dari cursor/connection.

Bad:

@Transactional
public StreamingOutput export() {
    ResultSet rs = repository.openCursor();
    return out -> writeCsv(rs, out); // transaction/connection hidden into response streaming
}

Ini berbahaya karena response writing bergantung pada client speed/proxy buffering.

13.3 Open Connection in Filter

Filter bukan tempat membuka connection per request secara implicit.

Risiko:

  • endpoint yang tidak butuh DB tetap meminjam connection;
  • pool cepat habis;
  • dependency tidak terlihat di code;
  • ordering filter membuat behavior sulit didiagnosis.

14. REST Error Contract for DB Failures

Database failure harus diterjemahkan ke HTTP contract secara defensible.

FailureHTTP CandidateNotes
unique constraint violation409 ConflictBusiness conflict if user actionable
FK violation409 or 422Depends on API contract
not found404If resource identity absent
deadlock/serialization conflict409 or 503Retry semantics must be explicit
pool exhausted503Service temporarily unavailable
DB connection refused503Dependency unavailable
query timeout503 or 504Depends on gateway/server role
invalid SQL/schema mismatch500Deployment/programming error
transaction rollback unexpected500 or domain-specificLog with incident ID

Rule:

Do not expose raw SQL exception messages to clients.

Expose stable problem response:

{
  "type": "https://errors.example.com/resource-conflict",
  "title": "Resource conflict",
  "status": 409,
  "code": "ORDER_ALREADY_APPROVED",
  "correlationId": "01HY...",
  "details": []
}

Log internal detail separately.


15. Exception Mapping Pattern

Jersey ExceptionMapper should map infrastructure exceptions carefully.

@Provider
public class SqlExceptionMapper implements ExceptionMapper<SQLException> {

    @Override
    public Response toResponse(SQLException ex) {
        String correlationId = Correlation.currentId();

        // In real systems, classify by SQLState/vendor code behind an abstraction.
        ErrorBody body = ErrorBody.of(
            "DATABASE_ERROR",
            "A database error occurred.",
            correlationId
        );

        return Response.status(Response.Status.SERVICE_UNAVAILABLE)
            .type(MediaType.APPLICATION_JSON_TYPE)
            .entity(body)
            .build();
    }
}

Namun jangan membuat mapper terlalu broad sehingga domain conflict berubah jadi 503 semua. Lebih baik repository/service menerjemahkan vendor-specific exception menjadi domain/infrastructure exception yang eksplisit.


16. Pool Exhaustion Failure Mode

Pool exhaustion biasanya tidak muncul sebagai “pool exhausted” di client. Client melihat latency/timeout/503/504.

Flow:

Observability signals:

  • active connections near max;
  • wait count/time increasing;
  • HTTP latency rising;
  • thread dump shows waits near datasource/pool;
  • DB CPU/lock/io wait increases;
  • p99 jumps before average;
  • proxy 504 increases.

Immediate mitigations:

  • fail fast with bounded pool wait;
  • reduce traffic;
  • degrade non-critical DB features;
  • disable expensive endpoint;
  • shorten query timeout;
  • add index/fix slow query if root cause known;
  • do not blindly increase pool across all instances.

17. Connection Leak Model

Leak berarti connection tidak dikembalikan ke pool.

Bad:

Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
return map(rs); // connection never closed if no finally/try-with-resources

Good:

try (Connection con = dataSource.getConnection();
     PreparedStatement ps = con.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
    return map(rs);
}

Leak symptoms:

  • active connection count monotonically rises;
  • throughput drops over time;
  • restart temporarily fixes issue;
  • thread dump shows many requests waiting for connection;
  • database sees idle connections held by app;
  • leak appears only on exception path.

Common leak causes:

  • no try-with-resources;
  • return Stream<T> backed by ResultSet;
  • exception path before close;
  • manual transaction path misses rollback/close;
  • async task captures connection;
  • framework integration misconfigured;
  • long streaming response holds cursor.

18. Statement and Query Timeout

Application-level timeout is not enough. A query can continue running in DB after client/proxy gives up unless driver/database cancels it.

Set timeouts at appropriate layers:

  • HTTP client timeout;
  • reverse proxy timeout;
  • GlassFish/server timeout;
  • JDBC pool wait timeout;
  • statement/query timeout;
  • transaction timeout;
  • database-side statement timeout if supported.

Example:

try (PreparedStatement ps = con.prepareStatement(sql)) {
    ps.setQueryTimeout(3); // seconds; behavior depends on driver/database
    // execute
}

Rule:

A request timeout without query timeout can still leave expensive database work running.


19. Transaction Timeout

Transaction timeout protects against transaction holding locks/resources too long.

If transaction timeout is longer than user-facing timeout, server may keep DB locks after client has gone away.

Bad:

Client timeout:       5s
Proxy timeout:       10s
Transaction timeout: 120s

Better:

Client timeout:       10s
Proxy timeout:        9s
App business budget:  8s
Transaction timeout:  6s for this use case
DB query timeout:     3s per query
Pool wait timeout:    200-500ms for latency-sensitive endpoint

Exact numbers depend on workload, but ordering must be intentional.


20. Read vs Write Pool Separation

Some systems benefit from separate datasource resources:

jdbc/orders-write
jdbc/orders-read
jdbc/reporting-readonly
jdbc/audit

Use when:

  • read replica exists;
  • reporting queries must not starve writes;
  • audit must be isolated;
  • endpoint classes have different latency profile;
  • database credentials differ by privilege.

Risks:

  • read-after-write consistency;
  • replica lag;
  • transaction cannot span read/write trivially;
  • operational complexity;
  • more pools to monitor.

Rule:

Pool separation is a bulkhead. Use it when failure isolation matters more than simplicity.


21. XA vs Non-XA Decision

JTA can coordinate multiple transactional resources, but distributed transactions are not free.

ScenarioRecommendation
Single database resourceUsually non-XA/local or simple JTA boundary is enough
Two databases must commit atomicallyXA may be considered, but evaluate design alternatives
DB + message broker atomicityOutbox often preferable to XA in modern systems
REST call + DB atomicityXA cannot make external HTTP call transactional
Audit write best-effortSeparate strategy, not necessarily same transaction

Trade-offs of XA:

  • more complex recovery;
  • driver/support requirements;
  • performance overhead;
  • operational debugging complexity;
  • in-doubt transaction handling.

Top-tier design question:

Do we truly need atomic commit across resources, or do we need a state machine with idempotency, outbox, and compensation?


22. Outbox Pattern Boundary

For REST command that must trigger external side effect:

Bad:

transaction begin
  update order
  call payment service
  insert audit
transaction commit

Better:

transaction begin
  update order state
  insert outbox event PAYMENT_CAPTURE_REQUESTED
transaction commit
async worker reads outbox
  call payment service with idempotency key
  update external action state

This avoids holding DB transaction during remote network call.


23. Health Check and Readiness Design

Naive health endpoint:

@GET
@Path("/health")
public String health() {
    try (Connection c = ds.getConnection()) {
        return "OK";
    }
}

Problems:

  • every probe borrows DB connection;
  • frequent probes can add load;
  • transient DB issue may restart healthy app unnecessarily;
  • liveness should not depend on DB availability;
  • readiness and liveness are different.

Better split:

EndpointPurposeDB Check?
/liveprocess is aliveNo, or minimal JVM check
/readyapp can serve trafficMaybe lightweight dependency check with cache/rate limit
/health/deepoperator diagnosticYes, guarded and not high-frequency

Readiness DB check should be:

  • cheap;
  • timeout bounded;
  • not run too frequently;
  • not start transaction;
  • not acquire long lock;
  • observable separately.

24. Reporting and Large Queries

REST endpoint for export/reporting is dangerous if it shares same pool as transactional commands.

Risk:

  • reporting query holds connection for minutes;
  • transactional endpoint waits;
  • pool full;
  • app appears down;
  • DB suffers heavy IO.

Better patterns:

  • async report generation;
  • reporting replica;
  • separate pool;
  • pagination/cursor with strict limit;
  • precomputed materialized view;
  • file/object storage for exports;
  • streaming with explicit operational budget.

Rule:

Long-running read paths should not silently share the same pool and timeout budget as command paths.


25. Database Credentials and Least Privilege

JDBC resource should reflect capability.

Examples:

jdbc/orders-write       -> can insert/update order tables
jdbc/orders-readonly    -> select only
jdbc/audit-append       -> insert audit only
jdbc/reporting-readonly -> select reporting schema

Benefits:

  • blast radius smaller;
  • accidental write blocked;
  • compromised endpoint less powerful;
  • audit clearer.

Costs:

  • more resources;
  • more credential management;
  • more tests;
  • more config as code.

In regulated systems, this trade-off often favors explicit resource separation.


26. Observability Signals

Minimum metrics/logs:

SignalWhy It Matters
active connectionspool saturation
idle connectionsover/under provisioning
wait count/timehidden latency before query
leak detection eventsconnection lifecycle bugs
query latencyDB work cost
transaction durationlock/resource hold time
rollback countdomain/infrastructure failure
SQL error classificationconflict vs dependency failure
thread statewhether HTTP workers wait on pool
endpoint p95/p99user impact

Correlate by:

  • endpoint;
  • correlation ID;
  • datasource/pool name;
  • SQL operation class, not raw SQL with PII;
  • transaction outcome;
  • DB host/cluster if applicable.

27. Logging Discipline

Log enough to debug:

correlationId=01HY...
endpoint=POST /orders/{id}/approve
pool=ordersPool
operation=approveOrder
sqlCategory=update-order-status
elapsedMs=43
outcome=success

Do not log:

  • raw credential;
  • full SQL with user PII;
  • card/secret/token values;
  • massive bind parameter dumps;
  • stack trace for expected domain conflict at error level.

Use structured logs. Avoid parsing prose.


28. Deployment Failure Model

SymptomLikely CauseDiagnosis
Deployment fails: resource not foundJNDI resource missinglist-jdbc-resources, app deployment log
ClassNotFoundException driverdriver jar not visibleclassloader/server lib strategy
Ping pool failsDB/network/credential/wrong classping-connection-pool, DB logs
Works dev, fails GlassFishlocal datasource differs from JNDIprofile/resource mismatch
Runtime 500 on first DB callpool resource exists but bad configserver log + pool ping + endpoint trace
Random stale connectionvalidation/idle timeout mismatchpool validation + DB network timeout
DB overloaded after scale outper-instance pool multipliedcompute total max connections

29. Resource Config as Code

Resource config should live beside deployment scripts.

Example structure:

ops/
  glassfish/
    00-domain.sh
    10-jvm.sh
    20-network.sh
    30-jdbc-orders.sh
    40-security.sh
    50-deploy.sh
  env/
    dev.env
    staging.env
    prod.env

30-jdbc-orders.sh should be:

  • idempotent or safely rerunnable;
  • no hardcoded secret;
  • target-aware;
  • version controlled;
  • reviewable;
  • tested on clean domain;
  • verified by smoke test.

Example pattern:

#!/usr/bin/env bash
set -euo pipefail

POOL_NAME="ordersPool"
JNDI_NAME="jdbc/orders"

if ! asadmin list-jdbc-connection-pools | grep -qx "$POOL_NAME"; then
  asadmin create-jdbc-connection-pool \
    --restype javax.sql.DataSource \
    --datasourceclassname "$DB_DATASOURCE_CLASS" \
    --property "serverName=$DB_HOST:portNumber=$DB_PORT:databaseName=$DB_NAME:user=$DB_USER:password=$DB_PASSWORD" \
    "$POOL_NAME"
fi

if ! asadmin list-jdbc-resources | grep -qx "$JNDI_NAME"; then
  asadmin create-jdbc-resource \
    --connectionpoolid "$POOL_NAME" \
    "$JNDI_NAME"
fi

asadmin ping-connection-pool "$POOL_NAME"

30. Testing Strategy

30.1 Unit Test

Test SQL mapping and classifier with fake/stub where possible.

30.2 Integration Test

Use real database container if possible:

  • schema migration applied;
  • datasource configured similarly;
  • transaction rollback behavior tested;
  • constraint violation mapping tested;
  • timeout behavior tested if feasible.

30.3 Runtime Smoke Test

After deploy to GlassFish:

  • app health endpoint passes;
  • JNDI lookup works;
  • pool ping passes;
  • simple read/write route works;
  • error mapper returns stable shape;
  • correlation ID appears in logs.

30.4 Load/Failure Test

Simulate:

  • DB slow query;
  • DB down;
  • pool exhaustion;
  • connection leak;
  • deadlock/serialization conflict;
  • transaction timeout;
  • rolling restart with active transactions.

31. Case Study: Slow Database Cascades to REST Outage

Situation

  • endpoint GET /orders/{id} normally p95 80ms;
  • DB index dropped during migration;
  • query now takes 4s;
  • HTTP thread pool 200;
  • JDBC pool 50;
  • proxy timeout 10s;
  • pool wait timeout 60s.

Failure Chain

Fix Strategy

Immediate:

  • reduce traffic or disable endpoint if needed;
  • lower pool wait timeout;
  • add query timeout;
  • restore index;
  • inspect DB locks/plans;
  • communicate degraded state.

Long-term:

  • migration guardrail for index removal;
  • query performance test;
  • p95/p99 alert;
  • pool wait alert;
  • endpoint bulkhead;
  • timeout budget alignment;
  • readonly/reporting pool separation if relevant.

32. Case Study: Transaction Holds Lock During Remote API Call

Bad Flow

POST /orders/{id}/approve
  begin transaction
  update order status
  call fraud API: 8s timeout
  insert audit
  commit

During fraud API slowness:

  • DB row lock held;
  • concurrent operations block;
  • transaction timeout fires;
  • client retries;
  • duplicate approval risk;
  • audit inconsistent.

Better Flow

POST /orders/{id}/approve
  begin transaction
  validate transition
  set order status = APPROVAL_PENDING_FRAUD_CHECK
  insert outbox event
  commit

worker:
  call fraud API with idempotency key
  update order final state

This turns uncertain synchronous transaction into explicit state machine.


33. Anti-Patterns

33.1 DriverManager Inside Resource Method

Bypasses container lifecycle and pooling.

33.2 Holding Connection Across Remote Calls

Turns network latency into DB lock/resource retention.

33.3 Pool Wait Longer Than User Timeout

Server continues waiting for a request the user abandoned.

33.4 One Pool for Everything

Reporting/export can starve command endpoints.

33.5 Health Check Hammering DB

Probe traffic itself becomes DB load.

33.6 Catch-All SQL Mapper to 500

Domain conflict and dependency failure become indistinguishable.

33.7 Transaction Boundary in Resource Method Everywhere

HTTP transport layer becomes transaction policy layer.

33.8 Returning Lazy/Cursor-backed Data to Serializer

Serialization phase accidentally holds DB resource.


34. Production Checklist

  • all datasource dependencies declared as JNDI names;
  • JDBC driver visibility documented;
  • pool/resource creation scripted;
  • no hardcoded DB credential in source;
  • pool max × instance count checked against DB capacity;
  • pool wait timeout bounded and below request timeout;
  • query timeout strategy exists;
  • transaction timeout strategy exists;
  • transaction boundary placed at service/use-case layer;
  • no remote call inside DB transaction unless explicitly justified;
  • connection lifecycle uses try-with-resources;
  • leak detection/monitoring enabled where appropriate;
  • pool metrics dashboard exists;
  • DB error classifier maps to stable REST errors;
  • health/readiness does not overload DB;
  • load test includes DB slowness and pool exhaustion;
  • operational runbook explains pool exhaustion response.

35. Mental Model Summary

A Jersey endpoint with database access is not this:

HTTP -> method -> SQL -> JSON

It is this:

HTTP request thread
  -> application service
  -> transaction boundary
  -> JNDI DataSource
  -> JDBC pool wait
  -> physical DB connection
  -> SQL execution/locks
  -> transaction outcome
  -> connection return
  -> DTO serialization
  -> response/error contract

Top-tier engineer does not only ask:

“Is the query correct?”

They ask:

“How long does the request hold a scarce resource, what happens when that resource is unavailable, and how does the failure propagate to users and operators?”


36. Practice Lab

Lab 1 — Configure JNDI DataSource

Create:

  • ordersPool;
  • jdbc/orders;
  • simple Jersey endpoint that does a lightweight query;
  • startup smoke test.

Verify app code has no DB URL.

Lab 2 — Pool Exhaustion Simulation

Set max pool small, e.g. 2. Create endpoint that holds connection for 3 seconds. Load test concurrency 20.

Observe:

  • pool wait;
  • HTTP latency;
  • thread dump;
  • error mapping;
  • proxy behavior.

Lab 3 — Connection Leak

Create intentionally broken endpoint that does not close connection. Enable leak detection/monitoring if available. Watch active connection count rise.

Then fix with try-with-resources.

Lab 4 — Transaction Boundary

Implement command endpoint:

  • validate input outside transaction;
  • mutate domain inside transaction;
  • map constraint violation to 409;
  • never expose SQL error message to client.

Lab 5 — Timeout Alignment

Set:

  • client timeout;
  • proxy timeout;
  • pool wait timeout;
  • query timeout;
  • transaction timeout.

Run DB slowness simulation and verify failure is fast and classified.


37. References


38. What Comes Next

Di bagian berikutnya, kita akan masuk ke GlassFish Security Realm, JAAS, Identity Store, App Security.

Kita akan membahas identity boundary di GlassFish: realm, JAAS legacy model, Jakarta Security, SecurityContext, role mapping, container-managed auth, application-managed auth, dan bagaimana security configuration berinteraksi dengan Jersey resource authorization.

Lesson Recap

You just completed lesson 22 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.