Pengenalan Package Database
Pada pemrograman Golang sebenarnya secara default itu memiliki sebuah package bernama database. Package ini memiliki kumpulan standar interface
yang menjadikan standar untuk komunikasi ke dalam database sehingga kita bisa langsung membuat program yang akan kita buat itu menggunakan package tersebut untuk mengakses beberapa jenis database apapun dengan kode yang sama. Tetapi yang akan membedakan adalah hanya kode query SQL
yang digunakan sesuai dengan database yang digunakan.
Pada kali ini kita akan fokus untuk mengintegrasikan ke dalam database
yang kita gunakan yaitu MySQL sebagai Database Management System. Jadi teman-teman usahakan sudah paham atau mempelajari terlebih dahulu dasar-dasar penggunaan MySQL.
Menambahkan Database Driver
Pastikan terlebih dahulu membuat projek folder terlebih dahulu dengan perintah tahapan dibawah ini:
- Buat folder
learn-golang-database-mysql
- Buka folder tersebut pada Visual Studio Code ataupun sejenisnya
- Buka terminal lalu ketik
go mod init learn-golang-database-mysql
untuk menginisialisasi projek golang dengango mod
.
Sebelum kita buat program menggunakan database, kita perlu tambahkan terlebih dahulu database driver-nya. Tanpa database driver, maka package database golang ini tidak akan mengerti apapun karena package driver ini digunakan untuk menerjemahkan interface kontrak yang ada pada golang. Maka kita bisa kunjungi https://golang.org/s/sqldrivers untuk melihat beberapa driver yang sudah ada dan mendukung pada program golang.
Pada driver MySQL terdapat 3 package yang bisa kita gunakan yaitu:
- MySQL: https://github.com/go-sql-driver/mysql/ [*]
- MySQL: https://github.com/siddontang/go-mysql/ [**] (also handles replication)
- MySQL: https://github.com/ziutek/mymysql [*]
Maka, kita bisa memilih salah satu dari ketiga package driver tersebut. Tentunya sesuai dengan kebutuhan teman-teman mana yang ingin digunakan. Saran kami, biasanya kita melihat dari populernya package tersebut dengan melihat jumlah star
dan fork
yang ada pada githubnya.
Tahapan selanjutnya yaitu tambahkan module Database MySQL driver ke dalam projek kita dengan mengetik seperti ini
go get -u github.com/go-sql-driver/mysql
maka hasilnya akan seperti dibawah ini
➜ integrasi-golang-database-mysql go get -u github.com/go-sql-driver/mysql
go: downloading github.com/go-sql-driver/mysql v1.7.1
go: added github.com/go-sql-driver/mysql v1.7.1
Selanjutnya, kita akan mengimport package driver tersebut di dalam program kita yang nantinya akan seperti ini
import(
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
Membuat Koneksi ke Database
Hal pertama yang perlu kita lakukan ketika membuat aplikasi menggunakan database yaitu bagaimana caranya agar bisa melakukan koneksi ke dalam databasenya. Koneksi ke dalam database pada golang itu kita bisa membuat objek sql.DB
dengan menggunakan fungsi sql.Open(driver, dataSourceName)
yang sudah kita import sebelumnya pada driver mysql
. Sedangkan dataSourceName
ini biasanya berisi penulisan koneksi ke dalam database tersebut. Berikut dibawah ini penulisannya.
<username>:<password>@tcp(<host>:<port>)/<database_name>
- : username yang sudah dibuat untuk dikoneksikan ke dalam program
- : kata sandi dari koneksi mysql
- : server/localhost database yang dituju yang akan digunakan
- : port database yang digunakan
- <database_name> : nama dari database yang akan kita koneksikan ke dalam program
Jika objek
sql.DB
sudah tidak digunakan lagi, saran kami untuk segera menutup koneksinya dengan menggunakan fungsiClose()
.
Kita coba langsung membuat fungsi main
dan ketik kode dibawah ini.
package main
import (
"database/sql"
)
func main() {
db, err := sql.Open("mysql", "root:belajargolang@tcp(localhost:3306)/belajar-golang")
if err != nil {
panic(err)
}
defer db.Close()
}
Pastikan teman-teman sudah tersedia MySQL Database pada laptop atau komputernya. Berikut yang perlu disiapkan:
- Username
root
dan passwordbelajargolang
ini bisa disesuaikan jika sebelumnya sudah ada username dan password bisa dipakai yang sudah ada. - Buat database
belajar-golang
pada MySQL Database yang mana nantinya akan kita pakai pada integrasi golang ini.
Saat kita jalankan program diatas, maka akan terjadi error panic yang mana sql: unknown driver "mysql"
ini merupakan error yang muncul ketika kita belum menambahkan package mysql
. Maka jangan lupa kita tambahkan package yang tadi sudah dibawah di awal yaitu penambahan package _ "github.com/go-sql-driver/mysql"
.
Database Pooling
sql.Db
pada Golang bukanlah sebuah koneksi ke dalam database melainkan sebuah pool ke database yang dikenal dengan konsep Database Pooling. Dalam hal tersebut, golang akan melakukan management koneksi ke databaes secara otomatis sehinga kita tidak perlu manage koneksi secara manual. Kemampuan database pooling ini kita juga bisa menentukan jumlah minimal dan maksimal koneksi yang dibuat oleh Golang agar kita tidak membanjiri koneksi ke database karena tiap database memiliki maksimal koneksi yang mengakses database tersebut.
Beberapa pengaturan database config pada sql.DB
diantaranya sebagai berikut:
Method | Keterangan |
---|---|
SetMaxIdleConns(number) | pengeturan berapa jumlah koneksi minimal yang dibuat |
SetmaxOpenConns(number) | pengaturan berapa jumlah koneksi maksimal yang dibuat |
SetConnMaxIdleTime(duration) | pengaturan berapa lama koneksi yang sudah tidak digunakan akan dihapus |
SetConnMaxLifetime(duration) | pengaturan berapa lama koneksi boleh digunakan |
Contohnya kita akan update connection diatas awalnya disimpan di main fungsi akan kita pisah menjadi satu fungsi GetConnection()
seperti ini
func GetConnection() *sql.DB {
db, err := sql.Open("mysql", "root:belajargolang@tcp(localhost:3306)/belajar-golang")
if err != nil {
panic(err)
}
db.SetMaxIdleConns(10)
db.SetMaxOpenConns(100)
db.SetConnMaxIdleTime(5 * time.Minute)
db.SetConnMaxLifetime(60 * time.Minute)
return db
}
Eksekusi Perintah SQL
Jika kita ingin komunikasi dengan database, maka kita pastikan bisa berkomunikasi dengan database dengan perintah SQL. Pada Golang sudah menyediakan function yang bisa kita gunakan untuk mengirim perintah SQL ke database menggunakan fungsi DB ExecContext(context,sql,params)
. Ketika kita mengirim perintah SQL, kita juga perlu mengirimkan context yang pernah kita pelajari sebelumnya agar kita bisa mengirim sinyal cancel juga ketika kita ingin membatalkan pengiriman perintah SQL-nya.
Selanjutnya kita coba buat table Customer dibawah ini
CREATE TABLE customer
(
id VARCHAR(100) NOT NULL,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
) ENGINE = InnoDB;
Mengirim Perintah SQL Insert
Setelah kita buat table customer
kita akan coba menyimpan data ke dalam database menggunakan perintah SQL Insert, berikut dibawah ini fungsi yang akan kita buat
func InsertIntoDB(ctx context.Context, db *sql.DB) {
_, err := db.ExecContext(ctx, "INSERT INTO customer(id,name) VALUES('santekno','Santekno');")
if err != nil {
panic(err)
}
fmt.Println("success insert data to database")
}
Maka untuk memanggil fungsi ini kita perlu update fungsi main menjadi seperti dibawah ini
func main() {
// initiate context
ctx := context.Background()
// initiate get connection to db
db := GetConnection()
defer db.Close()
// get function insert into database
InsertIntoDB(ctx, db)
}
Mengirim Perintah Query SQL
Operasi SQL tanpa membutuhkan hasil return
dari database kita cuku menggunakan perintah Exec
atau ExecContext
, namun jika kita membutuhkan hasil dari perintah SQL tersebut seperti SELECT
maka kita bisa menggunakan fungsi yang berbeda yaitu QueryContext(context,sql,params)
.
Baiklah, kita langsung buat saja fungsi untuk mengambil data dari database seperti kode dibawah ini.
func GetDataCustomer(ctx context.Context, db *sql.DB) {
rows, err := db.QueryContext(ctx, "SELECT id, name FROM customer")
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var id, name string
err := rows.Scan(&id, &name)
if err != nil {
panic(err)
}
fmt.Println("Id : ", id)
fmt.Println("Name: ", name)
}
}
Jika kita lihat diatas hasil dari query fungsi tersebut disimpan ke dalam data struct sql.Rows
. Rows ini digunakan untuk melakukan interasi terhadap hasil dari query, kita juga bisa melakukan interasi tersebut dengan menggunakan fungsi rows.Next()
untuk menampilkan datanya jika return data false artinya sudah tidak ada lagi data yang ada dalam result
. Lalu, kalau kita ingin membaca tiap datanya kita bisa gunakan rows.Scan(column...)
dan jangan lupa juga rows tersebut perlu kita tutup dengan menggunakan rows.Close()
.
Untuk memanggil fungsi mengambil data dari database diatas kita perlu memanggil fungsi diatas pada fungsi main
.
func main() {
// initiate context
ctx := context.Background()
// initiate get connection to db
db := GetConnection()
defer db.Close()
// get function insert into database
// InsertIntoDB(ctx, db)
// get data customer
GetDataCustomer(ctx, db)
}
Kita komentar dulu pemanggilan fungsi insert
data agar kita bisa mencoba untuk mengambil dari database. Maka jika kita sudah menjalankan hasilnya akan seperti dibawah ini
✗ go run main.go
Id : santekno
Name: Santekno
Mengenal Tipe Data Column
Kita juga perlu memahami lebih mendalam untuk tabel-tabel dengan tipe data kolom tertentu, saat ini pada projek ini hanya menggunakan tipe data varchar
yang mana biasanya kita gunakan untuk tipe data string
pada Golang.
Untuk mengenal lebih dalam lagi, kita akan coba menambahkan beberapa kolom yang sudah kita buat pada tabel customer
. Berikut ini kita tambahkan atau coba jalankan di SQL terminal untuk mengubah beberapa kolom tambahan.
DELETE FROM customer;
ALTER TABLE customer
ADD COLUMN email VARCHAR(100),
ADD COLUMN balance INTEGER DEFAULT 0,
ADD COLUMN rating DOUBLE DEFAULT 0.0,
ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN birth_date DATE,
ADD COLUMN married BOOLEAN DEFAULT false;
Beberapa tipe data yang akan kita pakai sebagai berikut.
Tipe Data Database | Tipe Data Golang |
---|---|
VARCHAR, CHAR | string |
INT, BIGINT | int32, int64 |
FLOAT, DOUBLE | float32, float64 |
BOOLEAN | bool |
DATE, DATETIME, TIME, TIMESTAMP | time.Time |
Selanjutnya kita juga akan memasukkan data customer
agar memiliki sampel data yang akan kita pakai nanti.
INSERT INTO customer(id, name, email, balance, rating, birth_date, married)
VALUES('santekno','Santekno','santekno@gmail.com',100000,5.0,'2000-01-01', true);
INSERT INTO customer(id, name, email, balance, rating, birth_date, married)
VALUES('ihsan','Ihsan Arif','ihsanarifr@gmail.com',100000,5.0,'2001-01-01', true);
Kila lihat juga pada fungsi yang telah kita buat pada GetDataCustomer
lalu ubah query tersebut menjadi seperti dibawah ini.
func GetDataCustomer(ctx context.Context, db *sql.DB) {
rows, err := db.QueryContext(ctx, "SELECT id, name, email, balance, rating, birth_date, married, created_at FROM customer")
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var id, name, email string
var balance int32
var rating float32
var birthDate, createdAt time.Time
var married bool
err := rows.Scan(&id, &name, &email, &balance, &rating, &birthDate, &married, &createdAt)
if err != nil {
panic(err)
}
fmt.Println("Id : ", id)
fmt.Println("Name: ", name)
fmt.Println("Email: ", email)
fmt.Println("Balance: ", balance)
fmt.Println("Rating: ", rating)
fmt.Println("Birth Date: ", birthDate)
fmt.Println("Married: ", married)
fmt.Println("Created At: ", createdAt)
}
}
Saat kita jalankan programnya maka akan terlihat error panic pada scan kolom seperti ini
panic: sql: Scan error on column index 5, name "birth_date": unsupported Scan, storing driver.Value type []uint8 into type *time.Time
goroutine 1 [running]:
main.GetDataCustomer({0x1006302a8?, 0x14000086000?}, 0x60?)
/Users/ihsanarifr/Documents/ihsan/learn-golang-database-mysql/main.go:59 +0x528
main.main()
/Users/ihsanarifr/Documents/ihsan/learn-golang-database-mysql/main.go:20 +0x70
exit status 2
Ternyata tipe data birth_date
ini tidak didukung oleh Golang saat menerjemahkan tipe data pada database dengan tipe data yang ada di Golang. Lalu kita harus bagaimana? Tenang ini terjadi karena format time pada database-nya tidak dilakukan penerjemahan ke dalam kode. Maka kita perlu ada tambahan saat koneksi ke database-nya seperti ini.
db, err := sql.Open("mysql", "root:belajargolang@tcp(localhost:3306)/belajar-golang?parseTime=true")
Secara default, Driver MySQL untuk Golang akan melakukan query tipe data DATE, DATETIME, TIMESTAMP menjadi []byte
atau []uint8
, dimana bisa dikonversikan menjadi string dengan melakukan parsing manual menjadi time.Time
. Konversi ini agak menyulitkan ketika kita memiliki kolom yang banyak dan ke depannya tidak scalable, maka ada cara lain yang bisa kita lakukan yaitu dengan menambahkan parameter parseTime=true
pada parameter connection.
Jalankan kembali programnya maka akan sukses dan mengeluarkan data seperti dibawah ini.
✗ go run main.go
Id : ihsan
Name: Ihsan Arif
Email: ihsanarifr@gmail.com
Balance: 100000
Rating: 5
Birth Date: 2001-01-01 00:00:00 +0000 UTC
Married: true
Created At: 2023-07-30 10:40:20 +0000 UTC
Id : santekno
Name: Santekno
Email: santekno@gmail.com
Balance: 100000
Rating: 5
Birth Date: 2000-01-01 00:00:00 +0000 UTC
Married: true
Created At: 2023-07-30 10:40:11 +0000 UTC
Pengenalan Tipe Data Nullable Type
Pada Golang menggunakan driver SQL Database tidak bisa memahami tipe data NULL di database. Maka khusus kolom yang memiliki NULL pada database akan menjadi masalah jika kita melakukan Scan
secara bulat menggunakan tipe data yang direpresentasikan pada Golang. Lalu kita harus bagaimana ya? Tenang ada caranya kok. Sebelum kepada cara, kita akan menambahkan terlebih dahulu data yang memiliki kolom yang NULL agar kita bisa mendapatkan studi kasus yang sesuai dengan insert data null pada database seperti ini.
INSERT INTO customer(id, name, email, balance, rating, birth_date, married)
VALUES('arif','Arif',NULL,100000,5.0,NULL, true);
Ketika kita jalankan program kita, maka akan menghasilkan error scan
pada kolom email
yang mana data Arif tadi memiliki email yang NULL.
✗ go run main.go
panic: sql: Scan error on column index 2, name "email": converting NULL to string is unsupported
goroutine 1 [running]:
main.GetDataCustomer({0x104d182a8?, 0x14000014090?}, 0x60?)
/Users/ihsanarifr/Documents/ihsan/learn-golang-database-mysql/main.go:59 +0x528
main.main()
/Users/ihsanarifr/Documents/ihsan/learn-golang-database-mysql/main.go:20 +0x70
exit status 2
Solusinya kita akan mengkonversikan secara otomatis NULL yang tidak didukung oleh Driver Mysql Golang dengan tipe khusus yang bisa NULL, yaitu ada pada package sql
dibawah ini.
Tipe Data Golang | Tipe Data Nullable |
---|---|
string | database/sql.NullString |
bool | database/sql.NullBool |
float64 | database/sql.NullFloat64 |
int32 | database/sql.NullInt32 |
int64 | database/sql.NullInt64 |
time.Time | database/sql.NullTable |
Maka, kita perlu mengubah sedikit variable-variabel yang memiliki NULL dari fungsi dari GetDataCustomer()
dan memberikan pengecekan pada data yang mengembalikan NULL menjadi seperti ini.
func GetDataCustomer(ctx context.Context, db *sql.DB) {
rows, err := db.QueryContext(ctx, "SELECT id, name, email, balance, rating, birth_date, married, created_at FROM customer")
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var id, name string
var email sql.NullString
var balance int32
var rating float32
var birthDate sql.NullTime
var createdAt time.Time
var married bool
err := rows.Scan(&id, &name, &email, &balance, &rating, &birthDate, &married, &createdAt)
if err != nil {
panic(err)
}
fmt.Println("Id : ", id)
fmt.Println("Name: ", name)
if email.Valid {
fmt.Println("Email: ", email)
}
fmt.Println("Balance: ", balance)
fmt.Println("Rating: ", rating)
if birthDate.Valid {
fmt.Println("Birth Date: ", birthDate)
}
fmt.Println("Married: ", married)
fmt.Println("Created At: ", createdAt)
}
}
Mengenal SQL Injection
Saat membuat query, kemungkinan kita akan melakukan hardcode
perintah SQL di kode Golang kita yang biasanya akan menerima input data dari user dari parameter lalu ditambahkan pada perintah SQL dari input user dan mengirimkan perintah tersebut menggunakan perintah query SQL.
Lalu apa sih SQL Injection itu? SQL Injection adalah sebuath teknik yang menyalahgunakan sebuah celah keamanan yang terjadi dalam lapisan basis data sebuah program. Biasanya dilakukan dengan mengirimkan input (biasanya parameter) dari pengguna dengan perintah yang salah sehingga menyebabkan hasil SQL yang kita buat menjadi tidak valid. SQL Injection ini sangat berbahaya jika kita salah membuat SQL yang bisa jadi tidak aman untuk program atau aplikasi kita dari serangan hacker.
Sebelum melakukan simulasi SQL Injection kita siapkan terlebih dahulu tabel yang nanti akan kita gunakan.
CREATE TABLE user(
username VARCHAR(100) NOT NULL,
password VARCHAR(100) NOT NULL,
PRIMARY KEY (username)
)ENGINE = InnoDB;
# insert data admin that sample data
INSERT INTO user(username,password) VALUES('admin','admin');
Sekarang, kita akan membuat sebuah fungsi yang mana akan melakukan query perinta SQL untuk mengambil data berdasarkan username
dan password
.
func Login(ctx context.Context, db *sql.DB, username, password string) bool {
sqlQuery := "SELECT username FROM user WHERE username='" + username + "' AND password='" + password + "' LIMIT 1"
rows, err := db.QueryContext(ctx, sqlQuery)
if err != nil {
panic(err)
}
defer rows.Close()
if rows.Next() {
var username string
rows.Scan(&username)
fmt.Println("Success Login ", username)
return true
} else {
fmt.Println("Failed Login")
}
return false
}
Lalu kita akan jalankan dengan menambahkan perintah ini pada fungsi main seperti berikut.
func main() {
// initiate context
ctx := context.Background()
// initiate get connection to db
db := GetConnection()
defer db.Close()
// get function insert into database
// InsertIntoDB(ctx, db)
// get data customer
// GetDataCustomer(ctx, db)
username := "admin"
password := "admin"
Login(ctx, db, username, password)
}
Maka hasilnya pun akan keluar seperti ini.
✗ go run main.go
Success Login admin
Lalu kita coba password-nya kita masukkan salah
, maka harusnya akan gagal melakukan login.
✗ go run main.go
Failed Login
Berikutnya kita akan langsung mencoba mengganti username dan passwordnya yang biasanya hacker gunakan untuk melakukan SQL Injection.
username := "admin'; #"
password := "salah"
Maka apa yang akan terjadi?
go run main.go
Success Login admin
Hasilnya pun user admin bisa login dengan password yang salah, ini adalah contoh dari SQL Injection yang mana sangat berbahaya ketika kita menggunakan query perintah SQL seperti dibawah ini. Lalu bagaimana cara mengatasinya dan solusina seperti apa?
- Jangan membuat SQL secara manual dengan menggabungkan
string
secara langsung - Jika kita membutuhkan parameter pada perintah SQL maka kita bisa menggunakan fungsi
Execute
atauQuery
dengan perintah yang akan kita bahas di chapter selanjutnya.
SQL dengan Parameter untuk menangani SQL Injection
Fungsi Exec can Query memiliki parameter tambahan yang bis akita gunakan untuk mensubstitusi parameter tersebut ke dalam SQL query yang kita buat. Maka SQL membutuhkan parameter untuk mengubahnya yaitu dengan menggunakan karakter ?
(tanda tanya).
Misalkan contoh query dibawah ini
- SELECT username FROM user WHERE username=? AND password=? LIMIT 1
- INSERT INTO user(username, password) VALUES(?,?)
- UPDATE USER SET password=? WHERE username=?
Nah kita coba ubah pada fungsi Login
query perintah SQL-nya disesuaikan dengan contoh diatas maka akan seperti ini.
sqlQuery := "SELECT username FROM user WHERE username=? AND password=? LIMIT 1"
rows, err := db.QueryContext(ctx, sqlQuery, username, password)
if err != nil {
panic(err)
}
Lalu ketika kita jalankan kembali program kita dengan username dan password ini.
username := "admin'; #"
password := "salah"
Maka hasilnya akan menjadi error dan gagal untuk melakukan login.
✗ go run main.go
Failed Login
Nah sekarang kita sudah berhasil menangani masalah terhadap SQL Injection yang akan dilakukan oleh beberapa hacker.
Kita coba juga perintah Exec
dengan membuat fungsi baru untuk menambahkan user
dengan kode dibawah ini.
func Register(ctx context.Context, db *sql.DB, username, password string) bool {
_, err := db.ExecContext(ctx, "INSERT INTO user(username, password) VALUE(?,?)", username, password)
if err != nil {
panic(err)
}
fmt.Println("success insert new user")
return true
}
Ketika kita sudah menambahkan fungsi tersebut maka kita panggil pada fungsi main seperti dibawah ini.
username := "santekno'; DROP TABLE user; #"
password := "santekno"
// Login(ctx, db, username, password)
Register(ctx, db, username, password)
Hasil dijalankannya program diatas maka akan terlihat sukses seperti ini.
✗ go run main.go
success insert new user
Dalam username terdapat perintah SQL untuk menghapus table dari user tetapi kita lihat pada database ternyata jika kita menggunakan parameter SQL, masukkan username tersebut akan menjadi string yang disimpan langsung sebagai username yang ada pada kolom username database sehingga ini menjadi aman ketika dijalankan dan perintah menghapus tabelnya pun tidak berlaku.
Mengambil Data ID dari Auto Increment
Kadang kita setelah melakukan penambahan data ke dalam database, kita juga ingin mengambil id auto increment yang dihasilkan, sebenarnya kita bisa menggunakan query ulang ke dalam database dengan menggunakan SELECT LAST_INSERT_ID()
, tetapi pada pemrograman bahasa Golang lebih dimudahkan yaitu kita tinggal menggunakan fungsi LastInsertId()
untuk mendapatkan ID terakhir yang dibuat secara auto increment. Biasanya kembalian dari fungsi Exec
itu memiliki object berupa Result
, dari Result
kita bisa memanggil fungsi LastInsertId()
tersebut untuk mengetahui ID terakhir setelah kita melakukan penambahan data ke dalam tabel.
Untuk melakukan simulasinya, kita perlu menambahkan tabel terlebih dahulu dengan perintah dibawah ini.
CREATE TABLE comments
(
id INT NOT NULL AUTO_INCREMENT,
email VARCHAR(100) NOT NULL,
comment TEXT,
PRIMARY KEY (id)
) ENGINE InnoDB;
Setelah tabel yang kita buat sudah tersedia pada database, kita lanjutkan dengan membuat fungsi yang mana melakukan penambahan data pada tabel comments dengan mengambil id
terakhir yang telah ditambahkan. Berikut fungsi yang akan kita buat dibawah ini.
func InsertComment(ctx context.Context, db *sql.DB, email, comment string) int64 {
sqlQuery := "INSERT INTO comments(email, comment) VALUES(?, ?)"
result, err := db.ExecContext(ctx, sqlQuery, email, comment)
if err != nil {
panic(err)
}
insertID, err := result.LastInsertId()
if err != nil {
panic(err)
}
return insertID
}
Setelah itu tambahkan pada fungsi main sebelum dijalankan program ini sebagai berikut.
email := "santekno@gmail.com"
comment := "hello world"
lastID := InsertComment(ctx, db, email, comment)
fmt.Println("Last Insert ID: ", lastID)
Maka hasilnya pun akan menampilkan id
terakhir saat melakukan penambahan data secara auto increment.
✗ go run main.go
Last Insert ID: 1
Preparing Statement
Prepare statement adalah fungsi query atau exec yang menggunakan parameter yang mana dibawahnya menggunakan prepare statement
. Prepare statement disiapkan terlebih dahulu setiap kita melakukan execute suatu query maka hanya berbeda parameternya saja misalkan saat kita melakukan insert data banyak secara langsung. Prepare statement bisa kita lakukan dengan manual tanpa harus menggunakan query atau exec parameter.
Saat kita membuat prepare statement maka secara otomatis akan mengenali koneksi database yang digunakan sehingga ketika kita mengeksekusi prepare statement berkali-kali maka akan menggunakan koneksi yang sama dan lebih efisien karena pembuatan prepare statementnya hanya sekali diawal saja.
Jika kita menggunakan query
dan exec
dengan parameter, kita tidak bisa menjalani bahwa koneksi yang digunakan akan sama, oleh karena itu bisa jadi prepare statement akan selalu dibuat berkali-kali walaupun kita menggunakan SQL yang sama. Cara untuk membuat prepare statement kita bisa menggunakan fungsi Prepare(context,sql)
denga direpresentasikan dalam struct database/sql.Stmt
. Seperti resource sql lain, sq.Stmt
ini juga harus di Close()
jika sudah tidak digunakan lagi.
Baiklah kita akan coba simulasikan menggunakan prepare statement dibawah ini.
func PrepareStatement(ctx context.Context, db *sql.DB) {
query := "INSERT INTO comments(email,comment) VALUE(?, ?)"
statement, err := db.PrepareContext(ctx, query)
if err != nil {
panic(err)
}
defer statement.Close()
for i := 0; i < 10; i++ {
email := "Santekno " + strconv.Itoa(i) + "@gmail.com"
comment := "Komentar ke " + strconv.Itoa(i)
result, err := statement.ExecContext(ctx, email, comment)
if err != nil {
panic(err)
}
id, err := result.LastInsertId()
if err != nil {
panic(err)
}
fmt.Println("comment id ", id)
}
}
Lalu selanjutnya untuk mencoba melakukan eksekusi perlu ditambahkan fungsi yang telah kita buat ke dalam fungsi main
seperti dibawah ini.
func main(){
..
..
PrepareStatement(ctx, db)
}
Prepare statement ini sangat berguna sekali ketika kita memiliki perulangan query ke dalam database yang mana query yang dipakai hanya mengubah parameter saja, maka untuk mengurangi pembuatan koneksi yang berlebihan maka dengan prepare statement koneksi yang dibuah hanya sekali saat prepare statement saja sehingga saat pada saat insert data, koneksi yang gunakan hanya sekali saat prepare statement.
Database Transaction
Fitur dari database yang sangat bermanfaat sekali dalam melakukan transaksi data ke dalam database. Secara default, semua perintah SQL yang kita kirim menggunakan Golang akan otomatis di commit
ke dalam database secara otomatis atau auto commit. Namun kita juga bisa menggunakan fitur transaksi ini dengan manual commit ke dalam database. Untuk memulai transaksi, kita bisa menggunakan fungsi Begin()
dan akan menghasilkan struct Tx
yang merepresentasikan transaksi. Struct Tx
akan kita gunakan sebagai pengganti DB untuk melakukan transaksi di setiap koneksi ke db misalkan Exec
, Query
, dan Prepare
. Setelah selesai transaksi kita akan menggunakan fungsi Commit()
untuk melakukan commit dan Rollback()
ketika kita ingin mengembalikan data tersebut ke semua karena suatu hal misalkan karena error disalah satu transaksinya.
Langung saja kita coba bagaimana mengimplementasikan dibawah ini.
func TransactionDatabase(ctx context.Context, db *sql.DB) {
tx, err := db.Begin()
if err != nil {
panic(err)
}
query := "INSERT INTO comments(email,comment) VALUE(?, ?)"
// do transaction
for i := 0; i < 10; i++ {
email := "Santekno " + strconv.Itoa(i) + "@gmail.com"
comment := "Komentar ke " + strconv.Itoa(i)
result, err := tx.ExecContext(ctx, query, email, comment)
if err != nil {
panic(err)
}
id, err := result.LastInsertId()
if err != nil {
panic(err)
}
fmt.Println("comment id ", id)
}
err = tx.Commit()
if err != nil {
panic(err)
}
}
Secara proses ini mirip saja ketika kita pakai ExecContext
biasa tetapi yang membedakan yaitu kita menggunakan transaksi manual yang mana kita bisa mengendalikan transaksi tersebut mau dijadikan apa saja. Misalkan ketika ada error disuatu transaksi maka tidak akan disimpan di database. Jika kita ingin mencoba eksekusi insert tetapi kita kembalikan ke semula bisa kita pakai tx.Rollback()
sehingga data yang sudah diinsert itu tidak akan disimpan ke dalam database.