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:
| Overload | Kapan 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:
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 SQLState | Arti |
|---|---|
08 | kegagalan koneksi (connection exception) |
22 | data tidak sesuai, misalnya potongan terlalu panjang |
23 | pelanggaran batasan integritas: PK duplikat, FK orphan, NOT NULL |
42 | galat sintaks atau akses SQL |
0A | fitur 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:
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(): 10061Mari kita telaah bagian inti program di atas:
DriverManager.setLoginTimeout(3): menetapkan batas waktu dalam detik untuk seluruh upayagetConnectionberikutnya. Tanpa batas ini, koneksi ke host yang menggantung dapat menahan thread sangat lama — nilai yang tersimpan dapat dibaca kembali lewatgetLoginTimeout().prop.setProperty("user", "budi"): kredensial dikemas sebagaiProperties, lalugetConnection(url, prop)meneruskannya kedriver.connect. Driver shim membuktikannya dengan mencetak nilaiuser— password kita sengaja cetak sebagai****sebagai kebiasaan menulis log yang aman.- Blok
try (...)tanpacatch:ConnectionmengimplementasikanAutoCloseable, sehinggatry-with-resources otomatis memanggilclose()— terlihat dari jejak[shim] Connection.close()— bahkan kalau terjadi exception di tengah blok. Karenamaintidak menangkap exception sukses ini, deklarasithrows SQLExceptionpadamainmenangani kasus tepi. throw new SQLException(alasan, "08001", 10061): konstruktor tiga argumen memuat pesan, SQLState standar (class08= kegagalan koneksi), dan kode vendor. Blokcatchlalu membongkar ketiganya lewatgetMessage(),getSQLState(), dangetErrorCode()— 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:
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 berkasimport 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;
}
}