Standard library Go tidak memuat kode khusus untuk PostgreSQL, MySQL, maupun SQLite. Sebagai gantinya, Go menyediakan satu package netral bernama database/sql yang mendefinisikan bagaimana aplikasi berbicara dengan database SQL, sementara bagaimana caranya berbicara dengan server tertentu diserahkan kepada driver pihak ketiga. Pemisahan dua peran tersebut membuat pola kode aplikasi seragam apa pun database di belakangnya. Pada chapter ini kita bedah arsitektur interface-dan-driver tersebut, membuktikan mekanismenya dengan program kecil, lalu mengulas method-method inti yang akan kita pakai di sepanjang section ini.
Antarmuka dan Driver: Arsitektur Dua Lapis #
Package database/sql hanya berisi tipe dan fungsi generik: sql.DB, sql.Rows, sql.Open, dan sebagainya. Detail protokol jaringan tiap database diimplementasikan oleh package driver melalui interface seperti driver.Driver dan driver.Conn dari subpackage database/sql/driver. Alur lengkapnya terlihat seperti berikut:
Kode aplikasi hanya memanggil database/sql; database/sql meminjam koneksi dari pool internalnya lalu mendelegasikan pekerjaan teknis ke driver; driver menerjemahkan perintah menjadi protokol khusus PostgreSQL. Kalau suatu hari Anda berpindah dari PostgreSQL ke MySQL, struktur kode pemanggilan tetap sama — yang berubah hanya import driver dan nama driver pada sql.Open.
sql.DB adalah Pool, Bukan Satu Koneksi #
Kesalahpahaman paling mahal bagi pemula adalah menganggap sql.DB sebagai satu koneksi database. Kenyataannya, sql.DB adalah penampung connection pool: objek ini menyimpan, membuat, dan mendaur ulang banyak koneksi di balik layar. Aplikasi cukup memegang satu sql.DB sejak startup hingga aplikasi mati, dan objek tersebut aman dipakai bersama oleh banyak goroutine secara serentak tanpa sinkronisasi tambahan.
Registrasi Driver dengan Blank Import #
Driver mendaftarkan dirinya ke database/sql lewat pemanggilan sql.Register. Karena pemanggilan tersebut terjadi di fungsi init() milik package driver, cukup mengimpor package driver tanpa memakai satu simbol pun — pola yang dikenal sebagai blank import:
import (
"database/sql"
_ "github.com/jackc/pgx/v5/stdlib"
)Tanda garis bawah menyatakan bahwa import dilakukan semata-mata untuk efek registrasi, bukan untuk memanggil fungsi. Setelah import tersebut, nama driver "pgx" tersedia untuk sql.Open. Beberapa driver umum di ekosistem Go:
| Database | Package driver | Nama untuk sql.Open |
|---|---|---|
| PostgreSQL | github.com/jackc/pgx/v5/stdlib | "pgx" |
| MySQL | github.com/go-sql-driver/mysql | "mysql" |
| SQLite | github.com/mattn/go-sqlite3 | "sqlite3" |
Membuktikan Arsitektur dengan Driver Tiruan #
Berkas contoh.go pada bagian bawah halaman mempraktikkan mekanisme registrasi tanpa database sungguhan: program mendefinisikan driver tiruan, mendaftarkannya, lalu mengamati perilaku sql.Open dan PingContext:
package main
import (
"context"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
)
// driverDemo adalah driver tiruan: tidak pernah benar-benar
// tersambung ke database mana pun.
type driverDemo struct{}
// Open dipanggil database/sql saat koneksi pertama dibutuhkan.
func (driverDemo) Open(dsn string) (driver.Conn, error) {
return nil, errors.New("driver demo tidak mendukung koneksi nyata")
}
func main() {
fmt.Println("driver terdaftar awal:", sql.Drivers())
sql.Register("demo", driverDemo{})
fmt.Println("driver terdaftar kini:", sql.Drivers())
db, err := sql.Open("demo", "host=localhost dbname=toko")
fmt.Println("err dari sql.Open :", err)
fmt.Println("db bernilai nil? :", db == nil)
err = db.PingContext(context.Background())
fmt.Println("err dari PingContext:", err)
}Output yang Dihasilkan:
driver terdaftar awal: []
driver terdaftar kini: [demo]
err dari sql.Open : <nil>
db bernilai nil? : false
err dari PingContext: driver demo tidak mendukung koneksi nyataMari kita telaah baris demi baris program di atas:
- Baris 13 (
type driverDemo struct{}): tipe kosong ini menjadi driver tiruan kita. - Baris 16-18 (
func (driverDemo) Open): implementasi interfacedriver.Driver. MethodOpensengaja mengembalikan error karena program ini memang tidak pernah tersambung ke database nyata. - Baris 21 (
sql.Drivers()): mencetak daftar driver terdaftar — kosong, buktidatabase/sqltidak membawa driver bawaan apa pun. - Baris 23 (
sql.Register): mendaftarkandriverDemodengan nama"demo", persis seperti yang dilakukan fungsiinit()pada driver asli. - Baris 26-28 (
sql.Open): pemanggilan berhasil — errornildan objekdbterbentuk — meskipun belum ada satu koneksi pun terjadi. - Baris 30-31 (
PingContext): ping memaksa pool meminta koneksi, barulah methodOpenmilik driver dipanggil dan error tiruan kita muncul. Inilah buktidatabase/sqlmurni mendelegasikan koneksi nyata ke driver.
Dua pengamatan terakhir pada output tersebut penting: sql.Open bersifat malas (baru menyimpan nama driver dan DSN), sedangkan koneksi baru benar-benar dibangun saat operasi pertama dijalankan. Konsekuensi dan cara memverifikasinya kita bahas tuntas pada chapter Koneksi Database
.
Selalu Gunakan Versi *Context #
Setiap method eksekusi di database/sql punya dua versi: biasa dan bervariasi *Context:
| Tanpa context | Dengan context | Peran |
|---|---|---|
db.Query | db.QueryContext | Membaca banyak baris |
db.QueryRow | db.QueryRowContext | Membaca satu baris |
db.Exec | db.ExecContext | Menjalankan tanpa hasil |
db.Ping | db.PingContext | Menguji koneksi |
db.Begin | db.BeginTx | Memulai transaksi |
Versi *Context menerima context.Context yang membawa batas waktu dan sinyal pembatalan — materi yang sudah kita kenal di chapter Context
. Ketika context dibatalkan, query yang sedang berjalan ikut dihentikan, baik di sisi aplikasi maupun di sisi server database. Karena itu, standar industri Go modern selalu memakai versi *Context untuk semua operasi database.
Dengan arsitektur dua lapis dan pool di tangan, kita siap membandingkan database/sql dengan alat bantu yang berdiri di atasnya. Chapter berikutnya mengulas sqlx serta dua pendekatan tetangganya, sqlc dan GORM.
Referensi #
- Dokumentasi package database/sql — referensi resmi seluruh tipe dan method.
- Opening a database handle — panduan resmi membuka handle database.
- pgx v5 stdlib — package driver PostgreSQL yang dipakai course ini.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.go
Unduh berkaspackage main
import (
"context"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
)
// driverDemo adalah driver tiruan: tidak pernah benar-benar
// tersambung ke database mana pun.
type driverDemo struct{}
// Open dipanggil database/sql saat koneksi pertama dibutuhkan.
func (driverDemo) Open(dsn string) (driver.Conn, error) {
return nil, errors.New("driver demo tidak mendukung koneksi nyata")
}
func main() {
fmt.Println("driver terdaftar awal:", sql.Drivers())
sql.Register("demo", driverDemo{})
fmt.Println("driver terdaftar kini:", sql.Drivers())
db, err := sql.Open("demo", "host=localhost dbname=toko")
fmt.Println("err dari sql.Open :", err)
fmt.Println("db bernilai nil? :", db == nil)
err = db.PingContext(context.Background())
fmt.Println("err dari PingContext:", err)
}