Start HereOrdered learning track

The Java SQL Stack: java.sql, javax.sql, Driver, Database, Network

Learn Java SQL, JDBC, Transactions, Connection Management & HikariCP - Part 002

Deep dive into the Java SQL stack: java.sql, javax.sql, JDBC drivers, DriverManager, DataSource, network boundaries, database sessions, and production implications.

14 min read2729 words
PrevNext
Lesson 0232 lesson track0106 Start Here
#java#jdbc#sql#datasource+4 more

Part 002 — The Java SQL Stack: java.sql, javax.sql, Driver, Database, Network

1. Tujuan Part Ini

Part ini membedah stack Java SQL/JDBC dari atas sampai bawah.

Kita akan menjawab:

  • Apa sebenarnya module java.sql?
  • Apa isi package java.sql?
  • Apa peran package javax.sql?
  • Mengapa production code sebaiknya memakai DataSource?
  • Bagaimana JDBC driver ditemukan dan dipakai?
  • Apa bedanya Java Connection, physical connection, socket, dan database session?
  • Di mana pooling masuk?
  • Kenapa detail ini penting untuk reliability?

Mental model ini wajib sebelum masuk ke Connection, Statement, transaction, dan HikariCP.


2. JDBC dalam Java Platform

JDBC adalah standard API Java untuk mengakses data source tabular, terutama relational database.

Pada Java modern, JDBC berada dalam module:

java.sql

Module ini mengekspor package penting:

java.sql
javax.sql

Secara praktis:

  • java.sql adalah core API untuk connection, statement, result set, transaction primitive, metadata, dan exception.
  • javax.sql melengkapi core API dengan server-side/data-source oriented API seperti DataSource, pooled connection, row set, dan event/listener tertentu.

Jangan tertipu oleh nama javax. Dalam konteks JDBC, javax.sql tetap bagian dari Java SE modern dan sangat relevan untuk aplikasi Java production.


3. High-Level Stack

Yang penting: JDBC bukan database. JDBC adalah contract antara Java code dan driver. Driver-lah yang memahami detail vendor database.

Misalnya:

  • PostgreSQL JDBC driver memahami PostgreSQL wire protocol.
  • MySQL Connector/J memahami MySQL protocol.
  • Microsoft JDBC Driver memahami SQL Server protocol.
  • Oracle JDBC driver memahami Oracle database protocol.

Kode aplikasi berbicara lewat interface umum. Driver menerjemahkan.


4. java.sql: Core JDBC API

Package java.sql menyediakan API untuk mengakses dan memproses data dari data source, umumnya relational database.

Core class/interface yang akan sering muncul:

TypePeran
DriverManagerService lama untuk mengelola driver dan membuat connection langsung
DriverInterface driver JDBC
ConnectionHandle ke database session / logical connection
StatementMenjalankan SQL tanpa parameter binding
PreparedStatementMenjalankan SQL dengan parameter binding
CallableStatementMemanggil stored procedure/function
ResultSetCursor hasil query
ResultSetMetaDataMetadata kolom result set
DatabaseMetaDataMetadata database/driver/schema capability
SQLExceptionException utama JDBC
SQLWarningWarning non-fatal dari operasi SQL
SavepointMarker rollback parsial dalam transaction
TypesKonstanta tipe SQL/JDBC
Date, Time, TimestampLegacy temporal JDBC types

4.1 Apa yang Harus Dipahami dari java.sql

Jangan memandang java.sql sebagai daftar class. Pahami sebagai model interaksi:

Connection adalah akar sebagian besar operasi. Dari connection kamu membuat statement, mengatur transaction, mengatur isolation, membaca metadata, dan mengelola savepoint.


5. javax.sql: DataSource-Oriented API

Package javax.sql melengkapi JDBC core API. Untuk aplikasi production, interface paling penting adalah:

javax.sql.DataSource

DataSource adalah factory untuk mendapatkan Connection.

Connection connection = dataSource.getConnection();

Secara desain, DataSource lebih cocok untuk production karena:

  • configuration bisa dikelola di satu tempat,
  • bisa dibungkus oleh connection pool,
  • bisa di-inject ke repository/service,
  • bisa diganti untuk test,
  • mendukung separation read/write datasource,
  • lebih mudah diobservasi,
  • lebih mudah diintegrasikan dengan container/framework.

5.1 Jenis DataSource

Secara konseptual ada beberapa bentuk:

JenisDeskripsi
Basic driver DataSourceMembuat physical connection langsung ke database
Pooled DataSourceMengembalikan logical connection dari pool
XA DataSourceDigunakan untuk distributed transaction / two-phase commit scenario

Untuk kebanyakan microservice modern, yang umum adalah pooled DataSource, misalnya melalui HikariCP.


6. DriverManager vs DataSource

6.1 DriverManager

DriverManager adalah service untuk mengelola sekumpulan JDBC driver dan membuat connection berdasarkan JDBC URL.

Contoh:

try (Connection connection = DriverManager.getConnection(
        "jdbc:postgresql://localhost:5432/app",
        "app_user",
        "secret")) {
    // use connection
}

Ini berguna untuk:

  • script kecil,
  • proof of concept,
  • tool lokal,
  • eksperimen driver,
  • contoh sederhana.

Tetapi untuk aplikasi production besar, menyebarkan DriverManager.getConnection() di banyak tempat biasanya buruk.

6.2 DataSource

DataSource memindahkan detail connection acquisition ke abstraction.

public final class CaseRepository {
    private final DataSource dataSource;

    public CaseRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Optional<String> findStatus(long caseId) throws SQLException {
        String sql = "select status from case_file where id = ?";

        try (Connection connection = dataSource.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)) {

            statement.setLong(1, caseId);

            try (ResultSet rs = statement.executeQuery()) {
                if (!rs.next()) {
                    return Optional.empty();
                }
                return Optional.of(rs.getString("status"));
            }
        }
    }
}

Repository tidak tahu apakah connection berasal dari:

  • driver langsung,
  • HikariCP,
  • test fake/stub,
  • read replica pool,
  • container-managed datasource,
  • wrapper observability.

Itulah nilai abstraction.

6.3 Comparison Table

AspectDriverManagerDataSource
Connection acquisitionStatic callInstance abstraction
ConfigurationSering tersebarBisa terpusat
Dependency injectionTidak naturalNatural
PoolingTidak built-in sebagai usage patternNatural via pooled DataSource
TestingLebih sulitMudah diganti
Observability wrapperSulit jika tersebarLebih mudah
Production recommendationHindari sebagai default app designPreferred baseline

7. JDBC Driver Discovery

Dulu, aplikasi sering memanggil:

Class.forName("org.postgresql.Driver");

Tujuannya memaksa driver class loaded agar mendaftarkan dirinya ke DriverManager.

Pada JDBC modern, driver discovery umumnya memanfaatkan Java service provider mechanism. Driver jar dapat menyediakan provider configuration sehingga DriverManager bisa menemukan driver yang tersedia.

Namun, sebagai engineer production, detail pentingnya bukan sekadar apakah perlu Class.forName. Detail pentingnya:

  • driver jar harus ada di classpath/module path,
  • versi driver harus kompatibel dengan Java dan database,
  • driver behavior bisa berbeda antar vendor,
  • driver punya configuration properties sendiri,
  • driver bisa menentukan default timeout, SSL behavior, prepared statement behavior, fetch behavior, dan timezone handling.

7.1 Driver adalah Runtime Dependency yang Serius

Jangan perlakukan driver sebagai dependency pasif.

Driver memengaruhi:

  • connection handshake,
  • authentication,
  • TLS,
  • socket options,
  • query protocol,
  • prepared statement implementation,
  • batch rewrite behavior,
  • generated keys,
  • timestamp conversion,
  • streaming result set,
  • cancellation,
  • error code mapping,
  • failover support.

Upgrade driver bisa mengubah behavior produksi. Maka driver upgrade harus dites seperti dependency kritikal.


8. JDBC URL

JDBC URL adalah string yang dipakai untuk memilih driver dan memberi tahu lokasi/config data source.

Format umum:

jdbc:<subprotocol>:<subname>

Contoh:

jdbc:postgresql://localhost:5432/enforcement
jdbc:mysql://localhost:3306/enforcement
jdbc:sqlserver://localhost:1433;databaseName=enforcement
jdbc:oracle:thin:@//localhost:1521/FREEPDB1

JDBC URL sering mengandung konfigurasi penting seperti:

  • host,
  • port,
  • database/schema/service name,
  • SSL mode,
  • socket timeout,
  • connect timeout,
  • timezone behavior,
  • prepared statement cache,
  • application name,
  • failover host,
  • replica routing.

8.1 Anti-Pattern: URL sebagai Tempat Sampah Konfigurasi

Masalah umum:

jdbc:postgresql://db/prod?ssl=false&connectTimeout=0&socketTimeout=0&ApplicationName=unknown

Jika semua konfigurasi diselipkan di URL tanpa standard, sulit dilakukan:

  • review security,
  • timeout governance,
  • environment comparison,
  • incident diagnosis,
  • safe rollout.

Lebih baik buat configuration object/properties yang eksplisit dan terdokumentasi.


9. Connection: Object, Socket, Session

Salah satu sumber bug terbesar adalah menyamakan Connection dengan satu hal saja.

java.sql.Connection bisa merepresentasikan beberapa level:

9.1 Java Connection Reference

Ini object yang kamu pegang di kode Java.

Connection connection = dataSource.getConnection();

Ia memiliki method seperti:

  • prepareStatement
  • setAutoCommit
  • commit
  • rollback
  • setTransactionIsolation
  • setReadOnly
  • setSchema
  • close

9.2 Logical Connection

Jika memakai pool, connection yang kamu terima biasanya proxy/wrapper.

Ketika kamu memanggil:

connection.close();

biasanya yang terjadi:

  • connection dikembalikan ke pool,
  • state tertentu di-reset,
  • physical socket tetap hidup,
  • connection bisa dipinjam lagi oleh request lain.

Ini sebabnya close() tetap wajib meskipun connection berasal dari pool.

9.3 Physical Connection

Physical connection adalah koneksi nyata ke database.

Biasanya melibatkan:

  • TCP socket,
  • TLS session jika aktif,
  • authentication,
  • database backend process/thread/session,
  • server-side resource.

Physical connection mahal untuk dibuat dibanding reuse, sehingga pooling berguna.

9.4 Database Session

Database session adalah state di sisi database yang terkait dengan connection.

Session dapat menyimpan:

  • current transaction,
  • isolation level,
  • current schema/catalog,
  • session variables,
  • temporary table,
  • prepared statement server-side,
  • lock yang sedang dipegang,
  • application name,
  • client address,
  • role/user.

Karena session state bisa melekat pada physical connection, pool harus berhati-hati mengembalikan connection ke kondisi bersih sebelum dipakai ulang.


10. Production Implication: Connection State Pollution

Bayangkan request A melakukan:

connection.setReadOnly(true);
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
connection.setSchema("reporting");

Lalu connection dikembalikan ke pool tanpa reset yang benar. Request B meminjam physical connection yang sama dan tiba-tiba berjalan dengan:

  • schema salah,
  • isolation terlalu tinggi,
  • read-only aktif,
  • auto-commit tidak sesuai.

Pool modern biasanya melakukan state tracking/reset, tetapi jangan desain aplikasi dengan mengandalkan magic. Buat state explicit dan scoped.

Pattern yang baik:

try (Connection connection = dataSource.getConnection()) {
    boolean oldReadOnly = connection.isReadOnly();
    int oldIsolation = connection.getTransactionIsolation();

    try {
        connection.setReadOnly(true);
        connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        // execute read work
    } finally {
        connection.setReadOnly(oldReadOnly);
        connection.setTransactionIsolation(oldIsolation);
    }
}

Dalam framework, sebagian ini dikelola transaction manager. Tetapi pemahaman manual tetap penting untuk debugging.


11. Network Boundary

JDBC call terlihat seperti method call biasa:

statement.executeQuery();

Tetapi sebenarnya itu bisa melibatkan network round-trip.

Akibatnya, JDBC operation dapat gagal karena:

  • DNS issue,
  • TCP connect timeout,
  • TLS handshake failure,
  • packet loss,
  • firewall idle timeout,
  • database restart,
  • failover,
  • socket read timeout,
  • network partition,
  • driver protocol error.

Maka SQLException bukan hanya “SQL salah”.


12. Database Backend Session

Ketika physical connection dibuat, database biasanya membuat backend session atau context untuk client tersebut.

Session ini dapat:

  • mengeksekusi statement,
  • menyimpan transaction state,
  • memegang lock,
  • mengalokasikan memory,
  • memakai worker/process/thread,
  • muncul di monitoring database.

Contoh observability yang sering dicari di database:

  • client address,
  • application name,
  • username,
  • query aktif,
  • wait event,
  • transaction start time,
  • lock wait,
  • idle in transaction,
  • backend PID/session ID.

Top-tier engineer menghubungkan Java-side metric dengan DB-side session.

Misalnya:

Java-side SignalDB-side Signal yang Perlu Dicari
Hikari active connections tinggisession aktif/idle/idle-in-transaction
pending threads tinggiDB worker saturated atau lock wait
query latency tinggislow query, wait event, missing index
timeout saat borrowconnection leak atau long query/transaction
deadlock exceptiondeadlock graph/log di database
socket timeoutnetwork issue atau DB tidak merespons

13. Stack dengan HikariCP

Jika memakai HikariCP, bentuk stack menjadi:

Yang perlu dipahami:

  • aplikasi memanggil HikariDataSource.getConnection(),
  • Hikari memberi proxy/logical connection,
  • proxy membungkus physical driver connection,
  • close() pada proxy mengembalikan connection ke pool,
  • Hikari mengelola lifecycle physical connection,
  • Hikari punya timeout, max lifetime, idle timeout, validation, leak detection, metrics.

Contoh minimal:

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/enforcement");
config.setUsername("app_user");
config.setPassword("secret");
config.setMaximumPoolSize(10);
config.setConnectionTimeout(2_000);
config.setPoolName("enforcement-api");

HikariDataSource dataSource = new HikariDataSource(config);

Jangan membuat HikariDataSource per request. Pool harus dibuat sebagai application-level resource dan ditutup saat aplikasi shutdown.


14. Threading Model

JDBC call umumnya blocking.

Thread aplikasi dapat blocking saat:

  • menunggu connection dari pool,
  • membuat physical connection,
  • mengirim query,
  • menunggu lock,
  • membaca result set,
  • menunggu commit fsync/replication tergantung DB config,
  • rollback,
  • menutup/validasi connection.

Implikasi:

  • JDBC latency mengikat thread aplikasi.
  • Pool exhaustion dapat membuat thread pool ikut penuh.
  • Thread pool yang penuh dapat menyebabkan cascading failure.
  • Reactive/non-blocking stack tidak otomatis menyelesaikan masalah jika driver JDBC tetap blocking.

15. Basic Direct JDBC Example

Contoh ini berguna untuk memahami API, bukan rekomendasi architecture final.

public final class DirectJdbcExample {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:postgresql://localhost:5432/enforcement";
        String user = "app_user";
        String password = "secret";

        try (Connection connection = DriverManager.getConnection(url, user, password);
             PreparedStatement statement = connection.prepareStatement(
                 "select id, case_number, status from case_file where id = ?"
             )) {

            statement.setLong(1, 1001L);

            try (ResultSet rs = statement.executeQuery()) {
                while (rs.next()) {
                    long id = rs.getLong("id");
                    String caseNumber = rs.getString("case_number");
                    String status = rs.getString("status");

                    System.out.printf("%d %s %s%n", id, caseNumber, status);
                }
            }
        }
    }
}

Kelebihan:

  • sederhana,
  • jelas untuk belajar,
  • resource cleanup eksplisit.

Kekurangan untuk production app:

  • credential/url hard-coded jika tidak dieksternalisasi,
  • tidak ada pooling,
  • tidak ada dependency injection,
  • tidak ada metrics,
  • tidak ada timeout governance,
  • sulit testing jika pola ini tersebar.

16. DataSource-Based Example

Lebih cocok untuk aplikasi server.

public final class CaseStatusDao {
    private final DataSource dataSource;

    public CaseStatusDao(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Optional<CaseStatusView> findStatus(long caseId) throws SQLException {
        String sql = """
            select id, case_number, status, updated_at
            from case_file
            where id = ?
            """;

        try (Connection connection = dataSource.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)) {

            statement.setLong(1, caseId);

            try (ResultSet rs = statement.executeQuery()) {
                if (!rs.next()) {
                    return Optional.empty();
                }

                return Optional.of(new CaseStatusView(
                    rs.getLong("id"),
                    rs.getString("case_number"),
                    rs.getString("status"),
                    rs.getObject("updated_at", OffsetDateTime.class)
                ));
            }
        }
    }
}

DataSource bisa berasal dari HikariCP:

public final class DataSourceFactory {
    public static DataSource create() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(System.getenv("JDBC_URL"));
        config.setUsername(System.getenv("JDBC_USER"));
        config.setPassword(System.getenv("JDBC_PASSWORD"));
        config.setMaximumPoolSize(10);
        config.setConnectionTimeout(2_000);
        config.setPoolName("case-service-main");
        return new HikariDataSource(config);
    }
}

Repository tidak berubah ketika kita mengganti direct driver connection menjadi pooled connection. Itu desain yang baik.


17. Common Misconceptions

17.1 “Connection Itu Murah”

Physical connection tidak murah. Ia bisa melibatkan:

  • DNS lookup,
  • TCP handshake,
  • TLS handshake,
  • authentication,
  • database process/thread/session allocation,
  • setup session state.

Membuat connection per query di aplikasi high-throughput adalah recipe untuk latency dan load buruk.

17.2 “Pool Besar Selalu Lebih Cepat”

Pool besar hanya memperbesar concurrency ke database. Jika DB bottleneck, pool besar bisa memperburuk latency.

Lebih banyak concurrent query dapat berarti:

  • lebih banyak lock contention,
  • lebih banyak CPU context switching,
  • lebih banyak memory per session,
  • lebih banyak queue di DB,
  • lebih buruk p99 latency.

17.3 “close() Berarti Socket Ditutup”

Pada pooled connection, close() biasanya berarti return ke pool. Physical socket mungkin tetap hidup.

Tetapi dari perspektif aplikasi, kamu tetap wajib memanggil close().

17.4 “SQLException Berarti Query Salah”

SQLException bisa berasal dari:

  • SQL syntax,
  • constraint,
  • timeout,
  • network,
  • connection closed,
  • deadlock,
  • failover,
  • authentication,
  • unsupported feature,
  • driver bug.

Klasifikasi error wajib.

17.5 “Auto-Commit Aman untuk Semua”

Auto-commit berguna untuk statement tunggal yang independent. Tetapi untuk use case multi-step, auto-commit bisa menghasilkan partial update.

Contoh buruk:

insertAssignment(caseId, assignee); // committed
insertAudit(caseId, "ASSIGNED");   // fails

Hasilnya assignment berubah tetapi audit tidak ada.


18. Production Design Questions

Sebelum menulis JDBC access layer, jawab pertanyaan berikut.

18.1 Connection Acquisition

  • Dari mana DataSource berasal?
  • Apakah memakai pool?
  • Berapa maximumPoolSize?
  • Berapa connectionTimeout?
  • Apakah ada pool terpisah untuk read/write/batch?
  • Bagaimana shutdown pool dilakukan?

18.2 Transaction Boundary

  • Siapa pemilik transaction?
  • Apakah DAO boleh commit sendiri?
  • Apakah transaction boleh melewati network call?
  • Apakah read operation butuh read-only transaction?
  • Isolation level default apa?

18.3 Timeout

  • Berapa timeout acquire connection?
  • Berapa statement timeout?
  • Berapa lock timeout?
  • Berapa socket timeout?
  • Bagaimana urutan timeout antar layer?

18.4 Observability

  • Apakah pool metrics diekspos?
  • Apakah slow query terlihat?
  • Apakah acquisition latency terlihat?
  • Apakah correlation ID masuk log?
  • Apakah DB bisa menunjukkan application name?

18.5 Error and Retry

  • Error mana yang retryable?
  • Apakah write operation idempotent?
  • Bagaimana duplicate dicegah?
  • Apakah deadlock di-retry?
  • Bagaimana ambiguous commit ditangani?

19. JDBC Stack Review Checklist

Gunakan checklist ini untuk review codebase.

19.1 Good Signals

  • Repository menerima DataSource via constructor/dependency injection.
  • Connection selalu ditutup dengan try-with-resources.
  • Statement dan result set selalu ditutup.
  • SQL value memakai parameter binding.
  • Transaction boundary ada di service/use-case layer.
  • Pool configuration eksplisit.
  • Timeout configuration eksplisit.
  • Metrics pool tersedia.
  • Driver version dikelola dan diuji.
  • Credential tidak hard-coded.

19.2 Warning Signals

  • DriverManager.getConnection() tersebar di banyak class.
  • Connection disimpan sebagai singleton field.
  • DAO melakukan commit/rollback sendiri tanpa orchestration.
  • Query dibangun dengan string concatenation dari user input.
  • Tidak ada timeout.
  • Tidak ada metrics pool.
  • Pool size besar tanpa reasoning.
  • Batch job memakai pool yang sama dengan API utama.
  • Long transaction melakukan HTTP call.
  • Semua SQLException diperlakukan sama.

19.3 Critical Smells

  • Connection leak di path exception.
  • autoCommit=false tanpa rollback di catch.
  • Retry write tanpa idempotency.
  • Pool exhausted tapi solusi hanya menaikkan pool size.
  • idle in transaction tinggi di database.
  • Query besar tanpa pagination/fetch strategy.
  • select * di endpoint latency-sensitive.
  • Migration berjalan saat traffic tanpa lock impact assessment.

20. Mini Drill

Drill 1 — Draw Your Stack

Gambar stack service kamu:

Endpoint / Consumer
  -> Service
  -> Transaction boundary
  -> Repository
  -> DataSource
  -> Pool
  -> Driver
  -> DB session
  -> Transaction / lock engine

Lalu jawab:

  • Di mana connection dipinjam?
  • Di mana connection dikembalikan?
  • Di mana transaction dimulai?
  • Di mana commit/rollback?
  • Di mana timeout diterapkan?
  • Di mana metrics diambil?

Drill 2 — Find Direct DriverManager Usage

Cari:

DriverManager.getConnection

Untuk setiap usage, klasifikasikan:

UsageAman?Kenapa?Perlu diganti DataSource?
CLI lokalmungkinscope kecilbelum tentu
service productionriskconfig/pooling sulitya
migration tooltergantunglifecycle berbedatergantung
test helpermungkinisolatedbelum tentu

Drill 3 — Identify Connection State

Cari usage:

setAutoCommit
setReadOnly
setTransactionIsolation
setSchema
setCatalog

Pastikan:

  • state diset untuk scope jelas,
  • state dikembalikan jika perlu,
  • framework transaction manager tidak dilanggar,
  • tidak ada hidden state pollution.

21. Ringkasan Part 002

Kita sudah membedah stack Java SQL/JDBC:

  • java.sql adalah core JDBC API.
  • javax.sql menyediakan abstraction penting seperti DataSource.
  • DataSource adalah baseline yang lebih baik untuk production dibanding menyebar DriverManager.
  • JDBC driver adalah runtime dependency penting yang menerjemahkan API Java ke database protocol.
  • Connection bisa berarti Java object, logical pooled handle, physical connection, socket, dan database session.
  • Network boundary membuat JDBC call bisa gagal karena banyak hal selain SQL error.
  • Connection state harus dipahami agar tidak terjadi state pollution.
  • HikariCP masuk sebagai pooled DataSource yang memberi logical connection dan mengelola physical connection.
  • JDBC call umumnya blocking, sehingga timeout dan pool design memengaruhi thread-level reliability.

Part berikutnya akan masuk ke JDBC Core Object Model: Connection, Statement, PreparedStatement, CallableStatement, ResultSet, metadata, dan exception sebagai object graph yang punya lifecycle dan ownership jelas.


References

Lesson Recap

You just completed lesson 02 in start here. 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.