Driver sudah berada di classpath dan URL sudah kita pahami — saatnya membuka jalur komunikasi yang sesungguhnya. Objek Connection adalah sesi antara program Java dan database: dari objek inilah lahir Statement, PreparedStatement, dan kontrol transaksi. Membuka koneksi juga merupakan momen paling rapuh dari sisi operasional: server bisa mati, kredensial bisa salah, jaringan bisa memutus. Chapter ini membahas cara membuka koneksi dengan tiga varian getConnection, mengatur kredensial dan timeout, lalu membaca petunjuk diagnostik pada SQLException ketika semuanya gagal.

Objek Connection dan Tiga Overload getConnection #

Semua koneksi dibuat lewat DriverManager.getConnection, yang tersedia dalam tiga bentuk:

OverloadKapan dipakai
getConnection(url)database tanpa autentikasi, misalnya SQLite lokal
getConnection(url, Properties info)kredensial plus opsi tambahan; paling lentur
getConnection(url, user, password)bentuk ringkas untuk dua kredensial saja

Bentuk kedua dan ketiga pada akhirnya sampai ke driver yang sama: bentuk ketiga mengemas user dan password ke dalam Properties sebelum memanggil driver.connect(url, info). Bentuk Properties unggul ketika kita perlu mengirim opsi ekstra — nama connection class Oracle, sslmode PostgreSQL, atau application name untuk pemantauan — tanpa menempelkannya di URL.

Kredensial dengan Properties #

Kredensial jangan pernah ditulis mati di dalam kode sumber. Berkas hasil kompilasi dapat dibongkar, dan riwayat git menyimpan setiap versinya. Praktik yang sehat mengambil rahasia dari lingkungan sistem operasi lalu memasukkannya ke Properties:

java Kredensial dari environment variable (butuh driver terpasang)
String url = System.getenv("DB_URL");          // jdbc:postgresql://host:5432/toko
Properties info = new Properties();
info.setProperty("user", System.getenv("DB_USER"));
info.setProperty("password", System.getenv("DB_PASSWORD"));
info.setProperty("sslmode", "require");

DriverManager.setLoginTimeout(5);              // batas 5 detik menyambung
try (Connection conn = DriverManager.getConnection(url, info)) {
    // bekerja dengan conn
}

Mendiagnosis Kegagalan dengan SQLException #

Semua kegagalan JDBC — koneksi ditolak, SQL salah ketik, constraint dilanggar — dilaporkan sebagai SQLException, turunan Exception yang membawa tiga sumber informasi. Method getMessage() memuat deskripsi dari driver, biasanya paling mudah dibaca manusia. Method getSQLState() mengembalikan kode lima karakter standar SQL; dua karakter pertama disebut class dan berlaku lintas database:

Class SQLStateArti
08kegagalan koneksi (connection exception)
22data tidak sesuai, misalnya potongan terlalu panjang
23pelanggaran batasan integritas: PK duplikat, FK orphan, NOT NULL
42galat sintaks atau akses SQL
0Afitur tidak didukung driver

Sementara itu, getErrorCode() memuat kode angka milik vendor (MySQL punya daftar sendiri, PostgreSQL juga) — berguna saat membaca dokumentasi database spesifik. Kombinasi ketiganya memungkinkan aplikasi mengambil keputusan cerdas: kegagalan class 08 layak dicoba ulang beberapa saat kemudian, sedangkan class 23 pada INSERT berarti data yang dikirim harus diperbaiki, bukan diulang.

Membuktikan Alur Koneksi #

Lingkungan uji Course ini tetap tanpa driver dan tanpa server database, jadi kita lanjutkan pola Chapter Arsitektur JDBC: driver shim menangani dua URL berbeda. URL jdbc:demo:toko berhasil dan mengembalikan Connection tiruan; URL jdbc:demo:galat membuat driver melempar SQLException berisi kode SQLState dan kode vendor seolah-olah host sungguhan tidak merespons:

import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.util.Properties;

public class DiagnosisKoneksi {

    public static void main(String[] args) throws SQLException {
        DriverManager.registerDriver(new DemoDriver());

        // Batas waktu menyambung: koneksi menggantung lebih dari 3 detik dibatalkan
        DriverManager.setLoginTimeout(3);
        System.out.println("loginTimeout  : " + DriverManager.getLoginTimeout() + " detik");

        // Skenario 1: koneksi berhasil dengan kredensial lewat Properties
        Properties prop = new Properties();
        prop.setProperty("user", "budi");
        prop.setProperty("password", "rahasia");
        try (Connection conn = DriverManager.getConnection("jdbc:demo:toko", prop)) {
            System.out.println("koneksi       : TERBUKA sebagai " + prop.getProperty("user"));
        }

        // Skenario 2: koneksi gagal, diagnosis lewat SQLException
        try {
            Connection gagal = DriverManager.getConnection("jdbc:demo:galat");
            System.out.println("baris ini tidak pernah tercetak: " + gagal);
        } catch (SQLException e) {
            System.out.println("koneksi       : GAGAL");
            System.out.println("getMessage()  : " + e.getMessage());
            System.out.println("getSQLState() : " + e.getSQLState());
            System.out.println("getErrorCode(): " + e.getErrorCode());
        }
    }
}

class DemoDriver implements Driver {

    @Override
    public Connection connect(String url, Properties info) throws SQLException {
        if (url == null || !url.startsWith("jdbc:demo:")) {
            return null;
        }
        if (url.endsWith(":galat")) {
            // meniru kegagalan jaringan nyata pada database sungguhan
            throw new SQLException(
                    "Koneksi ditolak: host demo.toko.local tidak merespons",
                    "08001", 10061);
        }
        String user = (info == null) ? "-" : info.getProperty("user", "-");
        System.out.println("[shim] connect(" + url + ") user=" + user + " password=****");
        return (Connection) Proxy.newProxyInstance(
                Connection.class.getClassLoader(),
                new Class<?>[]{Connection.class},
                (proxy, method, argumen) -> {
                    if (method.getName().equals("close")) {
                        System.out.println("[shim] Connection.close()");
                        return null;
                    }
                    if (method.getName().equals("toString")) {
                        return "ShimConnection";
                    }
                    Class<?> t = method.getReturnType();
                    if (t == boolean.class) return false;
                    if (t == int.class) return 0;
                    return null;
                });
    }

    @Override
    public boolean acceptsURL(String url) {
        return url != null && url.startsWith("jdbc:demo:");
    }

    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
        return new DriverPropertyInfo[0];
    }

    @Override
    public int getMajorVersion() {
        return 1;
    }

    @Override
    public int getMinorVersion() {
        return 0;
    }

    @Override
    public boolean jdbcCompliant() {
        return false;
    }

    @Override
    public java.util.logging.Logger getParentLogger() {
        return null;
    }
}

Output yang Dihasilkan:

text
loginTimeout  : 3 detik
[shim] connect(jdbc:demo:toko) user=budi password=****
koneksi       : TERBUKA sebagai budi
[shim] Connection.close()
koneksi       : GAGAL
getMessage()  : Koneksi ditolak: host demo.toko.local tidak merespons
getSQLState() : 08001
getErrorCode(): 10061

Mari kita telaah bagian inti program di atas:

  1. DriverManager.setLoginTimeout(3): menetapkan batas waktu dalam detik untuk seluruh upaya getConnection berikutnya. Tanpa batas ini, koneksi ke host yang menggantung dapat menahan thread sangat lama — nilai yang tersimpan dapat dibaca kembali lewat getLoginTimeout().
  2. prop.setProperty("user", "budi"): kredensial dikemas sebagai Properties, lalu getConnection(url, prop) meneruskannya ke driver.connect. Driver shim membuktikannya dengan mencetak nilai user — password kita sengaja cetak sebagai **** sebagai kebiasaan menulis log yang aman.
  3. Blok try (...) tanpa catch: Connection mengimplementasikan AutoCloseable, sehingga try-with-resources otomatis memanggil close() — terlihat dari jejak [shim] Connection.close() — bahkan kalau terjadi exception di tengah blok. Karena main tidak menangkap exception sukses ini, deklarasi throws SQLException pada main menangani kasus tepi.
  4. throw new SQLException(alasan, "08001", 10061): konstruktor tiga argumen memuat pesan, SQLState standar (class 08 = kegagalan koneksi), dan kode vendor. Blok catch lalu membongkar ketiganya lewat getMessage(), getSQLState(), dan getErrorCode() — pola yang persis sama saat Anda menghadapi database nyata.

Timeout dan Siklus Hidup Koneksi #

Koneksi adalah resource mahal: di balik layar, driver membuka soket TCP, menjalankan jabat tangan autentikasi, dan kadang menegosiasikan TLS — semuanya sebelum SELECT pertama. Karena itu koneksi wajib ditutup tepat saat selesai, dan try-with-resources adalah alat utamanya. Cuplikan berikut adalah wujud nyata pola tersebut pada SQLite, ditampilkan sebagai contoh karena driver sqlite-jdbc tidak terpasang pada lingkungan uji kita:

java Koneksi SQLite dengan try-with-resources (butuh driver xerial)
String url = "jdbc:sqlite:toko.db";
try (Connection conn = DriverManager.getConnection(url)) {
    System.out.println("Koneksi ke SQLite terbuka.");
    try (Statement stmt = conn.createStatement()) {
        stmt.executeUpdate("CREATE TABLE IF NOT EXISTS pelanggan (id INTEGER PRIMARY KEY, nama TEXT)");
    }
} catch (SQLException e) {
    System.err.println("SQLState " + e.getSQLState() + ": " + e.getMessage());
}

Koneksi yang terbuka dan tertutup dengan rapi kini menjadi kemampuan kita. Objek Connection yang dipegang masih diam — chapter berikutnya menghidupkannya dengan Statement untuk mengirim SQL SELECT, INSERT, UPDATE, dan DELETE yang sesungguhnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

DiagnosisKoneksi.java

Unduh berkas
java
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.util.Properties;

public class DiagnosisKoneksi {

    public static void main(String[] args) throws SQLException {
        DriverManager.registerDriver(new DemoDriver());

        // Batas waktu menyambung: koneksi menggantung lebih dari 3 detik dibatalkan
        DriverManager.setLoginTimeout(3);
        System.out.println("loginTimeout  : " + DriverManager.getLoginTimeout() + " detik");

        // Skenario 1: koneksi berhasil dengan kredensial lewat Properties
        Properties prop = new Properties();
        prop.setProperty("user", "budi");
        prop.setProperty("password", "rahasia");
        try (Connection conn = DriverManager.getConnection("jdbc:demo:toko", prop)) {
            System.out.println("koneksi       : TERBUKA sebagai " + prop.getProperty("user"));
        }

        // Skenario 2: koneksi gagal, diagnosis lewat SQLException
        try {
            Connection gagal = DriverManager.getConnection("jdbc:demo:galat");
            System.out.println("baris ini tidak pernah tercetak: " + gagal);
        } catch (SQLException e) {
            System.out.println("koneksi       : GAGAL");
            System.out.println("getMessage()  : " + e.getMessage());
            System.out.println("getSQLState() : " + e.getSQLState());
            System.out.println("getErrorCode(): " + e.getErrorCode());
        }
    }
}

class DemoDriver implements Driver {

    @Override
    public Connection connect(String url, Properties info) throws SQLException {
        if (url == null || !url.startsWith("jdbc:demo:")) {
            return null;
        }
        if (url.endsWith(":galat")) {
            // meniru kegagalan jaringan nyata pada database sungguhan
            throw new SQLException(
                    "Koneksi ditolak: host demo.toko.local tidak merespons",
                    "08001", 10061);
        }
        String user = (info == null) ? "-" : info.getProperty("user", "-");
        System.out.println("[shim] connect(" + url + ") user=" + user + " password=****");
        return (Connection) Proxy.newProxyInstance(
                Connection.class.getClassLoader(),
                new Class<?>[]{Connection.class},
                (proxy, method, argumen) -> {
                    if (method.getName().equals("close")) {
                        System.out.println("[shim] Connection.close()");
                        return null;
                    }
                    if (method.getName().equals("toString")) {
                        return "ShimConnection";
                    }
                    Class<?> t = method.getReturnType();
                    if (t == boolean.class) return false;
                    if (t == int.class) return 0;
                    return null;
                });
    }

    @Override
    public boolean acceptsURL(String url) {
        return url != null && url.startsWith("jdbc:demo:");
    }

    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
        return new DriverPropertyInfo[0];
    }

    @Override
    public int getMajorVersion() {
        return 1;
    }

    @Override
    public int getMinorVersion() {
        return 0;
    }

    @Override
    public boolean jdbcCompliant() {
        return false;
    }

    @Override
    public java.util.logging.Logger getParentLogger() {
        return null;
    }
}