summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2025-11-26 10:36:23 +0700
committerShulhan <ms@kilabit.info>2025-12-05 01:07:27 +0700
commitb6009906d0359cfc44e7a8ef67371ab52f30aa00 (patch)
tree69fa4fdc5793f5d1ecaf9f750def4b3dd5f6a238
parenta7c3ec9685f01663bd0ddeabec85842b7fad4a4a (diff)
downloadgolang-id-web-b6009906d0359cfc44e7a8ef67371ab52f30aa00.tar.xz
tutorial: terjemahkan "Tutorial: Accessing a relational database"
Tutorial asli ada di https://go.dev/doc/tutorial/database-access .
-rw-r--r--_content/doc/index.adoc8
-rw-r--r--_content/doc/tutorial/database-access/data-access/create-tables.sql16
-rw-r--r--_content/doc/tutorial/database-access/data-access/go.mod5
-rw-r--r--_content/doc/tutorial/database-access/data-access/go.sum2
-rw-r--r--_content/doc/tutorial/database-access/data-access/main.go124
-rw-r--r--_content/doc/tutorial/database-access/index.adoc735
-rw-r--r--go.work6
-rw-r--r--go.work.sum17
8 files changed, 913 insertions, 0 deletions
diff --git a/_content/doc/index.adoc b/_content/doc/index.adoc
index 9bde86a..be75eec 100644
--- a/_content/doc/index.adoc
+++ b/_content/doc/index.adoc
@@ -161,3 +161,11 @@ dihasilkan oleh penulisan ke variabel yang sama dalam goroutine yang berbeda.
=== link:/proposal/[Proposal^]
Dokumentasi proposal perubahan pada bahasa Go dalam Bahasa Inggris.
+
+
+== Mengakses basis-data
+
+=== link:/doc/tutorial/database-access/[Tutorial: Mengakses basis-data relasional^]
+
+Tutorial ini memperkenalkan dasar-dasar mengakses basis-data relasional
+dengan Go menggunakan paket `database/sql` dari pustaka baku.
diff --git a/_content/doc/tutorial/database-access/data-access/create-tables.sql b/_content/doc/tutorial/database-access/data-access/create-tables.sql
new file mode 100644
index 0000000..63290a0
--- /dev/null
+++ b/_content/doc/tutorial/database-access/data-access/create-tables.sql
@@ -0,0 +1,16 @@
+DROP TABLE IF EXISTS album;
+CREATE TABLE album (
+ id SERIAL,
+ title VARCHAR(128) NOT NULL,
+ artist VARCHAR(255) NOT NULL,
+ price DECIMAL(5,2) NOT NULL,
+ PRIMARY KEY (id)
+);
+
+INSERT INTO album
+ (title, artist, price)
+VALUES
+ ('Blue Train', 'John Coltrane', 56.99),
+ ('Giant Steps', 'John Coltrane', 63.99),
+ ('Jeru', 'Gerry Mulligan', 17.99),
+ ('Sarah Vaughan', 'Sarah Vaughan', 34.98);
diff --git a/_content/doc/tutorial/database-access/data-access/go.mod b/_content/doc/tutorial/database-access/data-access/go.mod
new file mode 100644
index 0000000..e33ed8d
--- /dev/null
+++ b/_content/doc/tutorial/database-access/data-access/go.mod
@@ -0,0 +1,5 @@
+module github.com/golang-id/web/data-access
+
+go 1.24.0
+
+require github.com/lib/pq v1.10.9
diff --git a/_content/doc/tutorial/database-access/data-access/go.sum b/_content/doc/tutorial/database-access/data-access/go.sum
new file mode 100644
index 0000000..aeddeae
--- /dev/null
+++ b/_content/doc/tutorial/database-access/data-access/go.sum
@@ -0,0 +1,2 @@
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
diff --git a/_content/doc/tutorial/database-access/data-access/main.go b/_content/doc/tutorial/database-access/data-access/main.go
new file mode 100644
index 0000000..588422c
--- /dev/null
+++ b/_content/doc/tutorial/database-access/data-access/main.go
@@ -0,0 +1,124 @@
+package main
+
+import (
+ "database/sql"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/lib/pq"
+)
+
+type Album struct {
+ ID int64
+ Title string
+ Artist string
+ Price float32
+}
+
+// albumsByArtist mengambil album-album berdasarkan nama artis.
+func albumsByArtist(db *sql.DB, name string) ([]Album, error) {
+ // albums adalah slice yang menyimpan data dari hasil kueri.
+ var albums []Album
+
+ rows, err := db.Query(`
+ SELECT id, title, artist, price
+ FROM album
+ WHERE artist = $1`, name)
+ if err != nil {
+ return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
+ }
+ defer rows.Close()
+
+ // Iterasi pada rows, menggunakan Scan untuk menyimpan data ke dalam
+ // struct Album.
+ for rows.Next() {
+ var alb Album
+ err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price)
+ if err != nil {
+ return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
+ }
+ albums = append(albums, alb)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
+ }
+ return albums, nil
+}
+
+// albumByID kueri album berdasarkan ID.
+func albumByID(db *sql.DB, id int64) (Album, error) {
+ // Variabel yang menyimpan baris kembalian dari basis-data.
+ var alb Album
+
+ row := db.QueryRow(`
+ SELECT id, title, artist, price
+ FROM album WHERE id = $1", id)
+ err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return alb, fmt.Errorf("albumsById %d: album tidak ditemukan", id)
+ }
+ return alb, fmt.Errorf("albumsById %d: %v", id, err)
+ }
+ return alb, nil
+}
+
+// addAlbum menambahkan sebuah album baru ke dalam basis-data dan
+// mengembalikan ID album yang baru.
+func addAlbum(db *sql.DB, alb Album) (int64, error) {
+ var id int64
+ err := db.QueryRow(`
+ INSERT INTO album (title, artist, price)
+ VALUES ($1, $2, $3)
+ RETURNING id`,
+ alb.Title, alb.Artist, alb.Price).Scan(&id)
+ if err != nil {
+ return 0, fmt.Errorf("addAlbum: %v", err)
+ }
+ return id, nil
+}
+
+func main() {
+ // Contoh DATABASE_URL = "postgres://username:password@localhost:5432/database_name"
+ databaseUrl := os.Getenv("DATABASE_URL")
+ var connector *pq.Connector
+ var err error
+ connector, err = pq.NewConnector(databaseUrl)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ var db *sql.DB
+ db = sql.OpenDB(connector)
+ defer db.Close()
+
+ err = db.Ping()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("Terhubung!")
+
+ albums, err := albumsByArtist(db, "John Coltrane")
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("Albums ditemukan: %v\n", albums)
+
+ // Tulis langsung ID 2 untuk menguji kueri.
+ alb, err := albumByID(db, 2)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("Album ditemukan: %v\n", alb)
+
+ albID, err := addAlbum(db, Album{
+ Title: "The Modern Sound of Betty Carter",
+ Artist: "Betty Carter",
+ Price: 49.99,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("ID dari album baru: %v\n", albID)
+}
diff --git a/_content/doc/tutorial/database-access/index.adoc b/_content/doc/tutorial/database-access/index.adoc
new file mode 100644
index 0000000..ebe38f4
--- /dev/null
+++ b/_content/doc/tutorial/database-access/index.adoc
@@ -0,0 +1,735 @@
+= Tutorial: Mengakses basis-data relasional
+
+Tutorial ini memperkenalkan dasar-dasar mengakses basis-data relasional
+dengan Go menggunakan paket `database/sql` dari pustaka baku.
+
+Anda akan lebih mudah mengikuti tutorial ini bila telah terbiasa dengan
+perkakas Go.
+Bila ini adalah pengalaman pertama Anda dengan Go, silahkan lihat
+link:/doc/tutorial/getting-started/[Memulai dengan Go]
+untuk pengenalan singkat.
+
+Paket
+https://pkg.go.dev/database/sql[`database/sql`^]
+memiliki beragam tipe dan fungsi untuk terhubung ke basis-data, mengeksekusi
+transaksi, membatalkan operasi yang sedang berjalan, dan banyak lagi.
+Untuk rincian tentang penggunaan paket, lihat
+link:/doc/database/[Mengakses basis-data].
+
+Pada tutorial ini, kita akan membuat sebuah basis-data, dan menulis kode
+untuk mengakses basis-data.
+Data yang akan kita gunakan pada proyek contoh ini yaitu album-album musik
+jazz lama.
+
+Dalam tutorial ini, kita akan melewati tahap-tahap berikut:
+
+. Membuat direktori untuk penyimpanan kode.
+. Menyiapkan sebuah basis-data.
+. Mengimpor _driver_ untuk basis-data.
+. Terhubung ke basis-data.
+. Mengambil banyak baris dari basis-data.
+. Mengambil sebuah baris dari basis-data.
+. Menambahkan data.
+
+NOTE: Untuk tutorial lainnya, lihat
+link:/doc/tutorial/[halaman Tutorial].
+
+[#prerequisites]
+== Kebutuhan
+
+* Pemasangan sistem manajemen basis-data (_database management system_ atau
+ DBMS)
+ https://www.postgresql.org/download/[PostgreSQL^].
+* Pemasangan Go.
+ Untuk instruksi pemasangan lihat
+ link:/doc/install/[Memasang Go].
+* Perkakas untuk menyunting kode.
+* Terminal untuk mengeksekusi perintah.
+ Go bekerja di terminal mana pun di sistem Linux dan Mac dan di
+ `PowerShell` atau `cmd` di Windows.
+
+[#create_folder]
+== Buat direktori untuk penyimpanan kode
+
+Untuk memulai, buatlah sebuah direktori untuk kode yang akan kita tulis.
+
+. Buka terminal dan pindah ke direktori pengguna Anda.
++
+--
+Pada Linux atau Mac:
+----
+$ cd
+----
+Pada Windows:
+----
+C:\> cd %HOMEPATH%
+----
+Selanjutnya pada tutorial ini kita akan menggunakan `$` sebagai tanda
+perintah terminal.
+Perintah yang akan tertera bisa berjalan pada Windows juga.
+--
+
+. Dari terminal, buatlah sebuah direktori bernama `data-access`.
++
+--
+----
+$ mkdir data-access
+$ cd data-access
+----
+--
+
+. Buat sebuah modul untuk menyimpan dependensi yang nanti kita tambahkan
+ selama tutorial.
++
+--
+Jalankan perintah `go mod init`, dengan parameter nama modul.
+----
+$ go mod init example/data-access
+go: creating new go.mod: module example/data-access
+----
+Perintah ini membuat berkas `go.mod` tempat dependensi yang nanti kita
+tambahkan akan tersimpan.
+Untuk informasi lanjut, lihat
+link:/doc/modules/managing-dependencies/[Manajemen dependensi].
+
+NOTE: Pada pengembangan sebenarnya, Anda sebaiknya membuat nama modul yang
+sesuai dengan lingkungan kerja Anda.
+Lebih lanjut, lihat
+link:/doc/modules/managing-dependencies#naming_module[Manajemen dependensi].
+--
+
+Selanjutnya kita akan membuat sebuah basis-data.
+
+[#set_up_database]
+== Menyiapkan sebuah basis-data
+
+Pada langkah ini, kita akan membuat basis-data yang akan kita gunakan selama
+tutorial ini.
+Kita akan menggunakan antar-muka perintah (_command-line interface_ atau
+CLI) yang disediakan oleh DBMS untuk membuat basis-data dan tabel, dan juga
+untuk menambahkan data.
+
+Kita akan membuat sebuah basis-data tentang album-album jazz lama.
+
+Perintah-perintah dalam bagian ini meggunakan
+https://www.postgresql.org/docs/18/app-psql.html[PostgreSQL psql],
+namun kebanyakan DBMS memiliki CLI mereka sendiri dengan fitur-fitur yang
+mirip.
+
+. Buka terminal yang baru.
+. Masuk ke DBMS, berikut contoh pada PostgreSQL.
++
+--
+----
+$ psql -U postgres
+postgres=#
+----
+--
+. Pada baris perintah `postgres=#`, buat lah sebuah basis-data.
++
+--
+----
+postgres=# create database recordings;
+----
+--
+
+. Pindah lah ke basis-data yang baru kita buat untuk membuat tabel.
++
+--
+----
+postgres=# \c recordings
+You are now connected to database "recordings" as user "postgres".
+recordings=#
+----
+--
+
+. Dalam direktori `data-access`, buatlah sebuah berkas bernama
+ `create-tables.sql` yang akan menyimpan skrip SQL untuk membuat
+ tabel-tabel.
+
+. Di dalam berkas tersebut, tempel kode SQL berikut, kemudian simpan berkas.
++
+--
+----
+DROP TABLE IF EXISTS album;
+CREATE TABLE album (
+ id SERIAL,
+ title VARCHAR(128) NOT NULL,
+ artist VARCHAR(255) NOT NULL,
+ price DECIMAL(5,2) NOT NULL,
+ PRIMARY KEY (`id`)
+);
+
+INSERT INTO album
+ (title, artist, price)
+VALUES
+ ('Blue Train', 'John Coltrane', 56.99),
+ ('Giant Steps', 'John Coltrane', 63.99),
+ ('Jeru', 'Gerry Mulligan', 17.99),
+ ('Sarah Vaughan', 'Sarah Vaughan', 34.98);
+----
+Dalam kode SQL ini, kita:
+
+* Menghapus (_drop_) sebuah tabel bernama `album`.
+ Mengeksekusi perintah ini terlebih dahulu membuat kita lebih mudah
+ menjalankan ulang skrip nantinya, seandainya kita akan ulang lagi dari awal.
+* Membuat sebuah tabel `album` dengan empat kolom: `id`, `title`, `artist`,
+ dan `price`.
+ Setiap nilai kolom `id` diisi oleh DBMS secara otomatis.
+* Menambahkan empat baris data ke dalam tabel `album`.
+--
+
+. Dari terminal `psql`, jalankan skrip yang baru kita buat tersebut.
++
+--
+Kita gunakan perintah `\\i` dengan cara berikut:
+----
+recordings=# \i create-tables.sql
+----
+--
+
+. Lewat terminal psql, gunakan perintah `SELECT` untuk memeriksa bahwa tabel
+ telah terbuat dan berisi data.
++
+--
+----
+recordings=# select * from album;
+ id | title | artist | price
+----+---------------+----------------+-------
+ 1 | Blue Train | John Coltrane | 56.99
+ 2 | Giant Steps | John Coltrane | 63.99
+ 3 | Jeru | Gerry Mulligan | 17.99
+ 4 | Sarah Vaughan | Sarah Vaughan | 34.98
+(4 rows)
+----
+--
+
+Selanjutnya, kita akan menulis kode Go untuk terhubung ke basis-data dan
+membaca data dari dalam tabel.
+
+[#import_driver]
+== Mengimpor _driver_ untuk basis-data
+
+Setelah kita memiliki sebuah basis-data yang berisi sebuah tabel dan data,
+saatnya mulai menulis kode Go.
+
+Untuk itu kita membutuhkan sebuah _driver_ basis-data yang akan
+menerjemahkan permintaan yang kita buat lewat fungsi-fungsi dalam paket
+`database/sql` menjadi permintaan yang dapat dipahami oleh basis-data.
+
+. Lewat peramban, kunjungi halaman wiki
+ https://go.dev/wiki/SQLDrivers[SQLDrivers^]
+ untuk menentukan _driver_ yang akan kita gunakan.
++
+--
+Gunakan daftar di halaman tersebut untuk menentukan _driver_ yang akan kita
+gunakan.
+Untuk mengakses PostgreSQL dalam tutorial ini, kita akan menggunakan modul
+https://github.com/lib/pq[github.com/lib/pq]
+--
+
+. Catat nama modul dari _driver_ -- yaitu, `github.com/lib/pq`.
+
+. Buat sebuah berkas `main.go` untuk menulis kode Go yang disimpan dalam
+ direktori `data-access`.
+
+. Dalam `main.go`, tempel kode berikut untuk mengimpor _driver_.
++
+--
+----
+package main
+
+import "github.com/lib/pq"
+----
+Dalam kode ini, kita:
+
+* Menambahkan berkas kode tersebut ke dalam paket `main` sehingga kita dapat
+ mengeksekusi-nya nanti.
+* Mengimpor _driver_ PostgreSQL `github.com/lib/pq`.
+--
+
+Setelah mengimpor _driver_, kita akan memulai menulis kode untuk mengakses
+basis-data.
+
+[#get_handle]
+== Terhubung ke basis-data
+
+Sekarang tulis kode Go yang menghubungkan Anda ke basis-data.
+
+Kita akan menggunakan _pointer_ ke struct `sql.DB`, yang merepresentasikan
+akses ke basis-data.
+
+=== Menulis kode
+
+. Dalam `main.go` di bawah kode `import` yang kita tambahkan sebelumnya,
+ tempel kode Go berikut untuk membuat koneksi ke basis-data.
++
+--
+----
+func main() {
+ // Contoh DATABASE_URL = "postgres://username:password@localhost:5432/database_name"
+ databaseUrl := os.Getenv("DATABASE_URL")
+ var connector *pq.Connector
+ var err error
+ connector, err = pq.NewConnector(databaseUrl)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ var db *sql.DB
+ db = sql.OpenDB(connector)
+ defer db.Close()
+
+ err = db.Ping()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("Terhubung!")
+ }
+----
+Dalam kode tersebut, kita:
+
+* Menggunakan
+ https://pkg.go.dev/github.com/lib/pq#NewConnector[`pq.NewConnector`] untuk
+ membuat penghubung yang menerima alamat basis-data.
+
+* Memeriksa kegagalan dari `pq.NewConnector`.
+ Fungsi ini bisa gagal bila alamat koneksi salah.
+ Untuk mempermudah kode, kita gunakan `log.Fatal` untuk mengakhiri
+ eksekusi dan mencetak galat ke layar.
+
+* Memanggil
+ https://pkg.go.dev/database/sql#OpenDB[`sql.OpenDB`]
+ untuk menginisialiasi variabel `db` dengan mengirim nilai kembalian dari
+ `NewConnector`.
+
+* Memanggil
+ https://pkg.go.dev/database/sql#DB.Ping[`DB.Ping`]
+ untuk memastikan bahwa koneksi ke basis-data bekerja.
+ Pada saat program dijalankan, pemanggilan `sql.OpenDB` bisa jadi tidak
+ langsung terhubung ke basis-data, bergantung kepada _driver_ yang
+ digunakan.
+
+* Memeriksa galat dari `Ping`, bila koneksi gagal.
+
+* Mencetak pesan bila `Ping` terhubung dengan sukses.
+--
+
+. Di bagian atas berkas `main.go`, di bawah deklarasi paket, impor
+ paket-paket yang kita butuhkan untuk mendukung kode yang telah kita tulis.
++
+--
+Bagian atas dari berkas seharusnya seperti berikut:
+----
+package main
+
+import (
+ "database/sql"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/lib/pq"
+)
+----
+--
+
+. Simpan berkas `main.go`
+
+=== Jalankan kode
+
+. Tambahkan modul _driver_ PostgreSQL sebagai dependensi.
++
+--
+Gunakan perintah
+https://go.dev/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them["`go get`"]
+untuk menambahkan modul "github.com/lib/pq" sebagai dependensi ke dalam
+modul kita.
+Gunakan argumen titik yang artinya "ambil semua dependensi kode dalam
+direktori ini".
+----
+$ go get .
+go: added github.com/lib/pq v1.10.9
+----
+Go mengunduh dependensi tersebut karena kita menambahkannya ke dalam
+deklarasi `import` pada langkah sebelumnya.
+Untuk informasi lanjut tentang pelacakan dependensi, lihat
+link:/doc/modules/managing-dependencies#adding_dependency[Menambahkan sebuah
+dependensi].
+--
+
+. Dari terminal, set variabel lingkungan `DATABASE_URL` untuk digunakan pada
+ program.
++
+--
+Pada Linux atau Mac:
+----
+$ export DATABASE_URL=postgresql://postgres@127.0.0.1/recordings?sslmode=disable
+----
+Pada Windows:
+----
+C:\> set DATABASE_URL=postgresql://postgres@127.0.0.1/recordings?sslmode=disable
+----
+--
+
+. Dari dalam direktori yang berisi `main.go`, jalankan kode dengan perintah
+ `go run` dengan argumen titik yang artinya "jalankan paket main di dalam
+ direktori ini".
++
+--
+----
+$ go run .
+Terhubung!
+----
+--
+
+Kita telah terhubung!
+Selanjutnya, kita akan mengambil beberapa baris dari basis-data.
+
+
+[#multiple_rows]
+== Mengambil banyak baris
+
+Pada bab ini, kita akan gunakan Go untuk mengeksekusi kueri SQL untuk
+mengembalikan banyak baris.
+
+Untuk perintah SQL yang mengembalikan banyak baris, kita gunakan method
+`Query` dari paket `database/sql`, kemudian mengiterasi baris-baris yang
+dikembalikan.
+(Kita akan belajar mengambil sebuah baris nantinya, dalam bab
+link:#single_row[Mengambil sebuah baris]).
+
+=== Menulis kode
+
+. Dalam `main.go`, sebelum `func main`, tempel definisi struct `Album`
+ berikut.
+ Kita akan menggunakan struct ini untuk menyimpan data yang dikembalikan
+ dari kueri.
++
+--
+----
+type Album struct {
+ ID int64
+ Title string
+ Artist string
+ Price float32
+}
+----
+--
+
+. Sebelum `func main`, tempel fungsi `albumsByArtist` untuk kueri ke
+ basis-data.
++
+--
+----
+// albumsByArtist mengambil album-album berdasarkan nama artis.
+func albumsByArtist(db *sql.DB, name string) ([]Album, error) {
+ // albums adalah slice yang menyimpan data dari hasil kueri.
+ var albums []Album
+
+ rows, err := db.Query(`
+ SELECT id, title, artist, price
+ FROM album
+ WHERE artist = $1`, name)
+ if err != nil {
+ return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
+ }
+ defer rows.Close()
+
+ // Iterasi pada rows, menggunakan Scan untuk menyimpan data ke dalam
+ // struct Album.
+ for rows.Next() {
+ var alb Album
+ err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price)
+ if err != nil {
+ return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
+ }
+ albums = append(albums, alb)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
+ }
+ return albums, nil
+}
+----
+Dalam kode tersebut, kita:
+
+* Mendeklarasikan sebuah slice `albums` bertipe `Album`.
+ Slice ini akan menyimpan baris-baris data kembalian dari basis-data.
+ Field-field pada struct berkorespondensi dengan nama dan tipe kolum pada
+ basis-data.
+* Menggunakan
+ https://pkg.go.dev/database/sql#DB.Query[`DB.Query`]
+ yang mengeksekusi perintah `SELECT` untuk mengueri album berdasarkan
+ nama artis.
++
+--
+Parameter pertama dari `Query` yaitu perintah SQL.
+Setelah perintah SQL, kita bisa mengirim nol atau lebih parameter dengan
+tipe apa pun, sebagai nilai dari parameter dalam perintah SQL.
+Dengan memisahkan perintah SQL dari nilai parameter (bukan dengan
+menggabungkannya dengan, katakan lah, `fmt.Sprintf`), kita membuat paket
+`database/sql` mengirim nilai terpisah dari teks SQL, menghindari resiko
+injeksi SQL.
+--
+
+* Menunda penutupan `rows` sampai fungsi keluar, supaya sumber yang
+ terpakai dapat dirilis kembali ke sistem.
+
+* Iterasi kembalian `rows`, menggunakan
+ https://pkg.go.dev/database/sql#Rows.Scan[`Rows.Scan`]
+ untuk mengisi setiap nilai baris kolom ke dalam field struct `Album`.
++
+--
+`Scan` menerima pointer ke variabel, tempat nilai kolom akan ditulis.
+Di sini kita mengirim pointer ke variabel `alb`, dibuat menggunakan operator
+`&`.
+`Scan` menulis lewat pointer untuk mengisi field pada struct.
+--
+
+* Di dalam iterasi, periksa kesalahan saat mengonversi nilai kolom ke dalam
+ field-field struct.
+
+* Di dalam iterasi, tambahkan nilai `alb` yang baru ke dalam slice `album`.
+
+* Setelah iterasi, periksa galat dari semua kueri, menggunakan
+ `rows.Err`.
+ Jika kueri gagal, satu-satunya cara memeriksa galat untuk mengetahui
+ apakah berhasil atau tidak hanyalah di sini.
+--
+
+. Perbarui fungsi `main` supaya memanggil `albumsByArtist`.
++
+--
+Di akhir `func main`, tambahkan kode berikut.
+----
+albums, err := albumsByArtist("John Coltrane")
+if err != nil {
+ log.Fatal(err)
+}
+fmt.Printf("Albums ditemukan: %v\n", albums)
+----
+Dalam kode di atas, kita:
+
+* Memanggil fungsi `albumsByArtist` yang ditambahkan sebelumnya, menyimpan
+ nilai kembalian ke variabel `albums`.
+
+* Mencetak hasil.
+--
+
+=== Menjalankan kode
+
+Dari terminal, dalam direktori yang berisi `main.go`, jalankan kode.
+
+----
+$ go run .
+Terhubung!
+Albums ditemukan: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
+----
+
+Selanjutnya, kita akan mengueri satu baris data.
+
+[#single_row]
+== Kueri satu baris data
+
+Pada bab ini, kita akan mengueri sebuah baris dalam basis-data.
+
+Untuk perintah SQL yang mengembalikan sebuah baris, kita
+dapat menggunakan `QueryRow`, yang lebih mudah daripada `Query`.
+
+
+=== Menulis kode
+
+. Di bawah `albumsByArtist`, tempel fungsi `albumByID` berikut,
++
+--
+----
+// albumByID kueri album berdasarkan ID.
+func albumByID(db *sql.DB, id int64) (Album, error) {
+ // Variabel yang menyimpan baris kembalian dari basis-data.
+ var alb Album
+
+ row := db.QueryRow(`
+ SELECT id, title, artist, price
+ FROM album WHERE id = $1", id)
+ err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return alb, fmt.Errorf("albumsById %d: album tidak ditemukan", id)
+ }
+ return alb, fmt.Errorf("albumsById %d: %v", id, err)
+ }
+ return alb, nil
+}
+----
+Dalam kode tersebut, kita:
+
+* Menggunakan
+ https://pkg.go.dev/database/sql#DB.QueryRow[`DB.QueryRow`]
+ untuk mengeksekusi perintah `SELECT` untuk mengueri sebuah album dengan ID
+ tertentu.
++
+--
+Fungsi `QueryRow` mengembalikan `sql.Row`.
+Fungsi `QueryRow` tidak mengembalikan sebuah `error`.
+Namun, ia akan mengembalikan sebuah `error` nanti saat `Row.Scan` dipanggil.
+--
+
+* Menggunakan
+ https://pkg.go.dev/database/sql#Row.Scan[`Row.Scan`]
+ untuk menyalin nilai kolom ke dalam field-field pada struct.
+
+* Memeriksa galat dari `Scan`.
++
+--
+Galat `sql.ErrNoRows` mengindikasikan bahwa kueri tidak mengembalikan baris.
+Biasanya, galat tersebut diganti dengan teks yang lebih berarti, seperti
+"album tidak ditemukan".
+--
+--
+
+. Perbarui `main` supaya memanggil `albumByID`.
++
+--
+Pada akhir `func main`, tambahkan kode berikut.
+----
+// Tulis langsung ID 2 untuk menguji kueri.
+alb, err := albumByID(2)
+if err != nil {
+ log.Fatal(err)
+}
+fmt.Printf("Album ditemukan: %v\n", alb)
+----
+Pada kode di atas, kita:
+
+* Memanggil fungsi `albumByID` yang baru ditambahkan.
+* Mencetak album yang dikembalikan.
+--
+
+=== Menjalankan kode
+
+Dari terminal, dalam direktori yang berisikan `main.go`, jalankan kode.
+
+----
+$ go run .
+Terhubung!
+Albums ditemukan: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
+Album ditemukan: {2 Giant Steps John Coltrane 63.99}
+----
+
+Selanjutnya, kita akan menambahkan sebuah album ke dalam basis-data.
+
+
+[#add_data]
+== Menambahkan data
+
+Pada bab ini, kita akan mengeksekusi perintah SQL `INSERT` untuk menambahkan
+sebuah baris baru pada basis-data.
+
+Kita telah melihat cara menggunakan perintah SQL `Query` dan `QueryRow`
+untuk mengambil data.
+Untuk mengeksekusi perintah SQL yang _tidak_ mengembalikan data, kita
+gunakan `Exec`.
+Khusus pada PostgreSQL, bila kita ingin mengambil ID yang baru dari hasil
+`INSERT` kita tetap harus menggunakan `QueryRow` diikuti dengan `Scan`.
+
+=== Menulis kode
+
+. Di bawah `albumByID`, tempel fungsi `addAlbum` berikut untuk mengisi
+ sebuah album baru ke basis-data, kemudian simpan `main.go`.
++
+--
+----
+// addAlbum menambahkan sebuah album baru ke dalam basis-data dan
+// mengembalikan ID album yang baru.
+func addAlbum(db *sql.DB, alb Album) (int64, error) {
+ var id int64
+ err := db.QueryRow(`
+ INSERT INTO album (title, artist, price)
+ VALUES ($1, $2, $3)
+ RETURNING id`,
+ alb.Title, alb.Artist, alb.Price).Scan(&id)
+ if err != nil {
+ return 0, fmt.Errorf("addAlbum: %v", err)
+ }
+ return id, nil
+}
+----
+Dalam kode ini, kita:
+
+* Menggunakan
+https://pkg.go.dev/database/sql#DB.QueryRow[`DB.QueryRow`]
+untuk mengeksekusi perintah `INSERT`.
+
+* Menerima ID dari baris yang baru diisi ke basis-data menggunakan
+ https://pkg.go.dev/database/sql#Row.Scan[`Row.Scan`]
+
+* Memeriksa galat dari pengambilan ID
+--
+
+. Perbarui `main` supaya memanggil fungsi `addAlbum`.
++
+--
+Pada akhir `func main`, tambah kode berikut.
+----
+albID, err := addAlbum(db, Album{
+ Title: "The Modern Sound of Betty Carter",
+ Artist: "Betty Carter",
+ Price: 49.99,
+})
+if err != nil {
+ log.Fatal(err)
+}
+fmt.Printf("ID dari album baru: %v\n", albID)
+----
+Pada kode yang baru ini, kita:
+
+* Memanggil `addAlbum` dengan sebuah album baru, dan menerima ID dari album
+ yang baru ke variabel `albID`.
+--
+
+=== Jalankan kode
+
+Dari terminal, di dalam direktori yang berisi `main.go`, jalankan kode.
+
+----
+$ go run .
+Terhubung!
+Albums ditemukan: [{1 Blue Train John Coltrane 56.99} {2 Giant Steps John Coltrane 63.99}]
+Album ditemukan: {2 Giant Steps John Coltrane 63.99}
+ID dari album baru: 5
+----
+
+
+[#conclusion]
+== Kesimpulan
+
+Selamat!
+Kita baru saja menggunakan Go untuk melakukan aksi-aksi sederhana dengan
+basis-data relasional.
+
+Rekomendasi topik-topik selanjutnya:
+
+* Lihatlah panduan
+ link:/doc/database/[data akses], yang mengikutkan informasi lanjut tentang
+ subjek-subjek yang kita pelajari di sini.
+
+* Jika Anda baru belajar Go, Anda akan menemukan praktik terbaik dijelaskan
+ dalam
+ link:/doc/effective_go/[Efektif Go]
+ dan
+ link:/doc/code/[Menulis kode Go].
+
+* https://tur.golang-id.org/welcome/1[Tur Go]
+ adalah pengenalan langkah demi langkah dari fundamental Go.
+
+
+[#completed_code]
+== Kode lengkap
+
+Bab ini berisi kode lengkap untuk aplikasi yang telah kita buat selama
+tutorial ini.
+
+----
+include::data-access/main.go[]
+----
diff --git a/go.work b/go.work
new file mode 100644
index 0000000..1f58057
--- /dev/null
+++ b/go.work
@@ -0,0 +1,6 @@
+go 1.24.0
+
+use (
+ .
+ ./_content/doc/tutorial/database-access/data-access
+)
diff --git a/go.work.sum b/go.work.sum
new file mode 100644
index 0000000..ca4e7d3
--- /dev/null
+++ b/go.work.sum
@@ -0,0 +1,17 @@
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
+golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
+golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
+golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
+golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
+golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
+golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
+golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=