Bahasa pemrogaman golang secara default memiliki sebuah package bernama database. Package database adalah package yang berisikan kumpulan standard interface yang menjadi standard untuk berkomunikasi ke database. Hal ini menjadikan kode program yang kita buat untuk mengakses jenis database apabun bisa menggunakan kode program yang sama. Yang berbeda hanya kode SQL yang perlu kita gunakan sesuai dengan database yang kita gunakan.
Database Driver
Sebelum kita membuat kode progrram menggunakan database di golang, kita wajib menambahkan driver database nya terlebih dahulu. Tanpa driver database, maka package database di golang tidak mengerti apapun, karena hanya berisi kontrak interface saja. Untuk melihat list driver database mysql di golang silakan mengunjungi laman https://golang.org/s/sqldrivers
Kali ini saya akan menggunakan driver dari https://github.com/go-sql-driver/mysql/
Tambahkan module nya dengan command : go get -u github.com/go-sql-driver/mysql
Selanjutnya lakukan import pada driver tersebut ke kode program kita

Membuat koneksi ke database
Hal yang pertama akan kita lakukan ketika membuat aplikasi yang akan menggunakan database adalah melakukan koneksi ke database nya. Untuk melakukan koneksi ke database di Golang, kita bisa membuat object sql.DB
menggunakan function sql.Open(driver, dataSourceName)
, untuk menggunakan database MySQL, kita bisa menggunakan driver mysql
. Sedangkan untuk dataSourceName, tiap database biasanya punya cara penulisan masing-masing, misal di MySQL, kita bisa menggunakan dataSourceName seperti berikut username:password@tcp(host:port)/database_name
. Jika object sql.DB
sudah tidak digunakan lagi, disarankan untuk menutupnya menggunakan function Close()
.
Cobalah lakukan open connection ke database dengan kode program berikut :
package belajar_golang_database
import (
"database/sql"
"testing"
_ "github.com/go-sql-driver/mysql"
)
func TestOpenConnection(t *testing.T) {
db, err := sql.Open("mysql", "root:root@tcp(localhost:3306)/belajar_golang_database")
if err != nil {
panic(err)
}
defer db.Close()
}

Silakan sesuaikan username, password, host & port mysql milik anda. Jika sudah membuat koneksi coba jalankan unit test diatas dan pastikan tidak terjadi error

Database pooling
sql.DB
di golang sebenarnya bukanlah sebuah koneksi ke database, melainkan sebuah pool ke database, atau dikenal dengan konsep database pooling. Di dalam sql.DB
golang melakukan management koneksi ke database secara otomatis. Hal ini menjadikan kita tidak perlu melakukan management koneksi database secara manual.
Dengan kemampuan database pooling ini, kita bisa menetukan jumlah minimal dan maksimal koneksi yang dibuat oleh golang, sehingga tidak membanjiri koneksi ke database, karena biasanya ada batas maksimal koneksi yang bisa ditangani oleh database yang kita gunakan.
Berikut adalah function-function yang terdapat pada object atau struct DB
Method | Keterangan |
---|---|
(DB) SetMaxIdleConns(number) | Pengaturan berapa jumlah koneksi minimal yang dibuat |
(DB) SetMaxOpenConns(number) | Pengaturan berapa jumlah koneksi maksimal yang dibuat |
(DB) SetConnMaxIdleTime(duration) | Pengaturan berapa lama koneksi yang sudah tidak digunakan akan dihapus |
(DB) SetConnMaxLifetime(duration) | Pengaturan berapa lama koneksi boleh digunakan |
Untuk melakukan koneksi ke database kita bisa membuat sebuah function dan sekaligus mengatur database pooling nya, sehingga kita tidak perlu melakukan banyak koneisi di tiap-tiap kode program kita, kita hanya perlu memanggil function yang sudah dibuat saja. Contohnya seperti berikut
package belajar_golang_database
import (
"database/sql"
"time"
)
func GetConnection() *sql.DB {
db, err := sql.Open("mysql", "root:root@tcp(localhost:3306)/belajar_golang_database")
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 database
Saat membuat aplikasi menggunakan database, sudah pasti kita ingin berkomunikasi dengan database menggunakan perintah SQL. Di Golang juga menyediakan function yang bisa kita gunakan untuk mengirim perintah SQL ke database menggunakan function (DB) ExecContext(context, sql, params)
. Ketika mengirim perintah SQL, kita butuh mengirimkan context, dan seperti yang sudah kita pelajari sebelumnya, dengan context kita bisa mengirim sinyal cancel jika kita ingin membatalkan pengiriman perintah SQL nya.
Untuk mencobanya silakan membuat table baru pada database belajar_golang_database
dengan command seperti berikut
CREATE TABLE customer (id VARCHAR(100) NOT NULL, name VARCHAR(100) NOT NULL, PRIMARY KEY (id)) ENGINE = InnoDB;
Jika sudah coba cek detail nya dengan command desc customer;

Setelah berhasil membuat table, selanjutnya buat gunakan baris kode berikut untuk melakukan insert ke database.
package belajar_golang_database
import (
"context"
"fmt"
"testing"
)
func TestExecSql(t *testing.T) {
db := GetConnection()
defer db.Close()
ctx := context.Background()
query := "INSERT INTO customer(id, name) VALUES('rendy', 'Rendy')"
_, err := db.ExecContext(ctx, query)
if err != nil {
panic(err)
}
fmt.Println("Success insert new customer")
}

Query SQL
Untuk operasi SQL yang tidak membutuhkan hasil, kita bisa menggunakan perintah Exec, namun jika kita membutuhkan result, seperti SELECT SQL
, kita bisa menggunakan function yang berbeda. Function untuk melakukan query database bisa menggunakan function (DB) QueryContext(context, sql, params)
.
Hasil query function adalah sebuah data structs sql.Rows
. Rows digunakan untuk melakukan interasi terhadap hasil dari query, kita bisa menggunakan function (Rows) Next (boolean)
untuk melakukan iterasi terhadap data hasil query, jikia return data false, artinya sudah tidak ada data lagi didalam result. Untuk membaca tiap data kita bisa menggunakan (Rows) Scan (columns...)
. Dan jangan lupa, setelah menggunakan Rows, jangan lupa untuk menutup nya menggunakan (Rows) Close()
.
Untuk mengambil data dari database dan melakukan iterasi untuk print data nya ke layar, kita dapat menggunakan baris kode berikut
package belajar_golang_database
import (
"context"
"fmt"
"testing"
)
func TestQuerySql(t *testing.T) {
db := GetConnection()
defer db.Close()
ctx := context.Background()
query := "SELECT id, name FROM customer"
rows, err := db.QueryContext(ctx, query)
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)
}
}

Coba jalankan kode program diatas, dan perhatikan outputnya

Tipe Data Column
Sebelumnya kita hanya membuat table dengan tipe data kolom nya berupa VARCHAR, untuk VARCHAR di database biasanya kita gunakan string di golang. Bagaimana dengan tipe data yang lain?
Apa representasi nya di golang , misal tipe data timestamp, date dan lain-lain.
Sebelum lanjut lebih jauh, silakan lakukan perubahan terlebih dahulu pada table customer dengan query berikut
mysql> DELETE FROM customer;
Query OK, 2 rows affected (0.00 sec)
mysql> 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 merried BOOLEAN DEFAULT false;
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0
Selanjutnya cek detail table dengan query DESC customer
.

Mapping tipe data
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 |
Setelah melakukan penambahan field pada table customer, selanjutkan lakukan penambahan beberapa data ke table customer dengan function yang sudah di buat sebelumnya, tetapi jangan lupe perbaiki query nya
func TestExecSql(t *testing.T) {
db := GetConnection()
defer db.Close()
ctx := context.Background()
query := "INSERT INTO customer(id, name, email, balance, rating, birth_date, merried) VALUES('rendy', 'Rendy', 'admin@rendy.dev', 1000000, 90.0, '1999-09-09', false)"
_, err := db.ExecContext(ctx, query)
if err != nil {
panic(err)
}
fmt.Println("Success insert new customer")
}

Jika sudah melakukan penambahan data ke database, selanjutnya kita akan mencoba melakukan iterasi pada data tersebut, yang mana datanya terdiri dari beberapa tipe data yang berbeda.
Untuk melakukan iterasi ke semua data, coba gunakan baris kode berikut ini
func TestQuerySql(t *testing.T) {
db := GetConnection()
defer db.Close()
ctx := context.Background()
query := "SELECT id, name, email, balance, rating, birth_date, merried, created_at FROM customer"
rows, err := db.QueryContext(ctx, query)
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var (
id, name, email string
balance int32
rating float64
birthDate, createdAt time.Time
married bool
)
err := rows.Scan(&id, &name, &email, &balance, &rating, &birthDate, &married, &createdAt)
if err != nil {
panic(err)
}
fmt.Println("=====================")
fmt.Println("Id :", id, "Name :", name, "Email :", email, "Balance :", balance, "Rating :", rating, "Birth Date:", birthDate, "Married :", married, "Created At :", createdAt)
}
}

Setelah membuat function diatas, coba jalankan kode program nya dan perhatikan output nya

Apabila anda mendapat error seperti diatas maka jangan panik, karena hal ini sudah expected terjadi.
Secara default, driver MySQL untuk Golang akan melakukan query tipe data DATE, DATETIME, TIMESTAMP menjadi []byte / []uint8. Dimana ini bisa di konversi menjadi String, lalu di parsing menjadi time.Time
. Namun hal ini merepotkan jika dilakukan manual, kita bisa meminta Driver MySQL untuk golang secara otomatis melakukan parsing dengan menambahkan menambahkan parameter parseDate=true
.
Untuk melakukan nya silakan ubah kode function GetConnection()
dan tambahkan parameter diatas pada dataSource
nya, contohnya seperti berikut
package belajar_golang_database
import (
"database/sql"
"time"
)
func GetConnection() *sql.DB {
db, err := sql.Open("mysql", "root:root@tcp(localhost:3306)/belajar_golang_database?parseTime=true")
if err != nil {
panic(err)
}
db.SetMaxIdleConns(10)
db.SetMaxOpenConns(100)
db.SetConnMaxIdleTime(5 * time.Minute)
db.SetConnMaxLifetime(60 * time.Minute)
return db
}

Setelah melakukan perubahan pada koneksi, coba jalankan kembali unit test TestQuerySqlComplex
dan pastikan sudah tidak terdapat error.

Nullable Type
Golang database tidak mengerti dengan tipe data NULL di database, oleh karena itu khusus untuk kolom yang bisa NULL di database, akan jadi maslah jika kita melakukan Scan secara bulat-bulat menggunakan tipe data representasinya di Golang.
Konversi secara otomatis NULL tidak di dukung oleh Driver MySQL golang, oleh karena itu khusus tipe kolom yang bisa NULL, kita perlu menggunakan tipe data yang ada dalam package SQL.
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.NullTime |
Untuk mengambil value dari tipe data Nullable, maka anda dapat menggunakan baris kode berikut
if email.Valid {
fmt.Println("Email :", email.String)
}
if birthDate.Valid {
fmt.Println("Birth Date :", birthDate.Time)
}

SQL Injection
Saat membuat aplikasi, kita tidak mungkin akan melakukan hardcode perintah SQL di Golang kita. Biasanya kita akan menerima input dari user, lalu membuat perintah SQL dari input user, dan mengirim menggunakan perintah SQL.
SQL Injection adalah teknik yang menyalahgunakan sebuah celah keamanan yang terjadi dalam lapisan basis data sebuah aplikasi. Biasanya, SQL Injection dilakukan dengan mengirim input dari user dengan perintah yang salah, sehingga menyebabkan hasil SQL yang kita buat menjadi tidak valid. SQL Injection sangat berbahaya, jika sampai kita salah membuat query SQL, bisa jadi data kita tidak aman.
Solusi untuk SQL Injection ini adalah dengan tidak membuat query SQL secara manual dengan menggabungkan String secara bulat-bulat. Ketika kita membutuhkan parameter ketika membuat SQL, kita bisa menggunakan function Execute atau Query dengan parameter.
SQL dengan parameter
Sekarang kita sudah tau bahayanya SQL Injection jika menggabungkan string ketika membuat query. Jika ada kebutuhan seperti itu, sebanarnya function Exec
dan Query
memiliki parameter tambahan yang bisa kita gunakan untuk mensubtitusi parameter dari function tersebut ke SQL query yang kita buat. Untuk menandai sebuah SQL membutuhkan parameter, kita bisa gunakan karakter ? (tanda tanya).
Contoh SQL query nya nanti akan menjadi seperti berikut

Penggunaan real Exec & Query with patameter, kurang lebih akan seperti baris kode berikut ini
func TestExecSqlParameter(t *testing.T) {
db := GetConnection()
defer db.Close()
ctx := context.Background()
id := "rendy"
name := "Rendy"
query := "INSERT INTO customer(id, name) VALUES(?, ?)"
_, err := db.ExecContext(ctx, query, id, name)
if err != nil {
panic(err)
}
fmt.Println("Success insert new customer")
}

Auto increment
Kadang kita membuat table dengan id auto increment, dan kadang pula kita ingin mengambil data id yang sudah kita insert ke dalam MySQL. Sebenarnya kita melakukan query ke database menggunakan SELECT LAST_INSERT_ID()
. Tapi untung nya di golang ada cara yang lebih mudah, kita bisa menggunakan function (RESULT) LastInsertId()
untuk mendapatkan id terakhir yang dibuat secara auto increment. Result
adalah object yang di kembalikan ketika kita menggunakan function Exec
.
Untuk mencoba nya, pertama buat table baru dengan query berikut pada MySQL
mysql> CREATE TABLE comments (id INT NOT NULL AUTO_INCREMENT, email VARCHAR(100) NOT NULL, comment TEXT, PRIMARY KEY(id)) ENGINE = InnoDB;
Selanjut nya cek detail table tersebut dengan command : desc comments

Setelah membuat table baru, saat nya kita mencoba fitur auto increment di golang, buat lah unit test baru seperti baris kode berikut ini untuk insert data ke table comments
func TestAutoIncrement(t *testing.T) {
db := GetConnection()
defer db.Close()
ctx := context.Background()
email := "admin@rendy.dev"
comment := "Test komen"
query := "INSERT INTO comments(email, comment) VALUES(?, ?)"
result, err := db.ExecContext(ctx, query, email, comment)
if err != nil {
panic(err)
}
insertId, err := result.LastInsertId()
if err != nil {
panic(err)
}
fmt.Println("Success insert new comment with id", insertId)
}

Coba jalankan unit test tersebut sebanyak 5x, kemudian perhatikan pada output id nya, jika taidak terdapat kesalahan maka output id nya akan bertambah secara increment dan hasil akhir nya akan menjadi 5, karena kita menjalankan program nya sebanayk 5x.

Untuk lebih pastinya, coba cek langsung di database mysql dengan query : SELECT * FROM comments;
mysql> SELECT * FROM comments;
+----+-----------------+------------+
| id | email | comment |
+----+-----------------+------------+
| 1 | admin@rendy.dev | Test komen |
| 2 | admin@rendy.dev | Test komen |
| 3 | admin@rendy.dev | Test komen |
| 4 | admin@rendy.dev | Test komen |
| 5 | admin@rendy.dev | Test komen |
+----+-----------------+------------+
5 rows in set (0.00 sec)
Pastikan id nya sudah auto increment seperti output diatas.
Prepare statement
Saat kita menggunakan Function Query atau EXEC yang menggunakan parameter, sebenarnya implementasi dibawahnya menggunakan Prepare Statement. Jadi tahapan pertama statement nya disiapkan terlebih dahulu, seteal itu baru di isi dengan parameter.
Kadang ada kasus kita ingin melakukan beberapa hal yang sama sekaligus, hanya berbeda parameternya, misal insert data langsung banyak. Pembuatan Prepare Statement bisa dilakukan dengan manual, tanpa harus menggunakan Query atau Exec dengan parameter.
Saat kita membuat Prepare Statement, 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 membuat prepare statement nya hanya sekali di awal saja. Jika menggunakan Query dan Exec parameter, kita tidak bisa menjamin bahwa koneksi yang digunakan akan sama, oleh karena itu, bisa jadi prepare statement akan selalu dibuat berkali-kali walaupun kita menggunakan SQL yang sama.
Untuk membuat Prepare Statement, kita bisa menggunakan function (DB) Prepare(context, sql)
. Prepare Statement direpresentasikan dalam struct database/sql.Stmt. Dan sama seperti resource sql lainnya, Stmt harus di Close() jika sudah tidak digunakan lagi.
Untuk mencoba Prepare Statement coba buat unit test baru dengan kode program berikut
func TestPrepareStatement(t *testing.T) {
db := GetConnection()
defer db.Close()
ctx := context.Background()
query := "INSERT INTO comments(email, comment) VALUES(?, ?)"
statement, err := db.PrepareContext(ctx, query)
if err != nil {
panic(err)
}
defer statement.Close()
for i := 0; i < 10; i++ {
email := "admin" + strconv.Itoa(i) + "@rendy.dev"
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("Commnet Id", id)
}
}

Coba jalankan kode program diatas dan pastikan outputnya sudah sesuai yaitu penambahan data ke database sebanyak 10 data secara auto increment.

Jadi apabila anda memiliki kebutuhan untuk menambahkan data dengan query yang sama tetapi hanya berbeda pada parameter nya saja maka saya rekomendasikan untuk menggunakan Prepare Statement ketimbang Exec atau Query.
Database transaction
Secara default, semua perintah SQL yang kita kirim menggunakan golang akan otomatis di commit, atau istilahnya auto commit. Namun kita bisa menggunakan fitur transaksi sehingga SQL yang kita kirim tidak akan otomatis di commit ke database.
Untuk memulai transaksi, kita bisa menggunakan function (DB) Begin()
, dimana akan menghasilkan struct Tx yang merupakan representasi Transaction. Struct Tx ini yang kita gunakan sebagai pengganti DB untuk melakukan transaksi, dimana hampir semua function di DB ada di Tx, seperti Exec, Query, atau prepare. Setelah selesai melakukan transaksi, kita bisa menggunakan function (Tx) Commit()
untuk melakukan commit atau Rollback()
.
Buat lah unit test baru dengan nama TestTransaction()
kemudian masukkan baris kode berikut
func TestTransaction(t *testing.T) {
db := GetConnection()
defer db.Close()
ctx := context.Background()
tx, err := db.Begin()
if err != nil {
panic(err)
}
query := "INSERT INTO comments(email, comment) VALUES(?, ?)"
// do transaction
for i := 0; i < 10; i++ {
email := "admin" + strconv.Itoa(i) + "@rendy.dev"
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("Commnet Id", id)
}
err = tx.Commit()
if err != nil {
panic(err)
}
}

Pada baris kode diatas kita melakukan transaction insert 10 data ke database, kemudian kita save dengan melakukan commit pada baris ke 29, jika anda merasa perlu membatalkan transaction dan merasa tidak perlu menyimpan ke database maka silakan lakukan Roleback()
pada baris ke 29.
Repository pattern
Dalam buku Domain-Driven, Eric Evans menjelaskan bahwa “Repository is a mechanism for encapsulating storage, retrieval, and search behavior, which emulates a collection of objects”. Pattern repository ini biasanya digunakan sebagai jembatan antar business logic aplikasi kita dengan semua perintah SQL ke database. Jadi semua perintah SQL akan ditulis di Repository, sedangkan business logic kode program kita hanya cukup menggunakan Repository tersebut. Pada golang Repository hanyalah kumpulan interface atau kontrak saja, nantinya kita juga akan membuat Repository Implementation untuk melakukan transaksi ke databasenya.
Dalam pemrogaman berorientasi object, biasanya sebuah table di database akan selalu dibuat representasinya sebagai class Entity atau Model, namun di golang karena tidak mengenal class jadi kita akan representasikan data dalam bentuk struct. Misal ketika kita query ke Repository, dibanding mengembalikan array, alangkah baiknya Repository melakukan konversi terlebih dahulu ke struct Entity / Model, sehingga kita tinggal menggunakan object nya saja.
Untuk mempraktek kan nya, pertama buatlah package baru dengan nama entity
dan didalam package tersebut buat file comment.go
sesuai dengan nama table kita sebelum nya. Kemudian di dalam file tersebut buat struct Comment dengan detail seperti berikut
package entity
type Comment struct {
Id int32
Email string
Comment string
}

Selanjutnya buat lagi package baru dengan nama repository
kemudian di dalam package tersebut buat juga file baru dengan nama comment_repository.go
dan isikan kode program berikut yang isinya adalah CommentRepository
interface.
package repository
import (
"belajar-golang-database/entity"
"context"
)
type CommentRepository interface {
Insert(ctx context.Context, comment entity.Comment) (entity.Comment, error)
FindById(ctx context.Context, id int32) (entity.Comment, error)
FindAll(ctx context.Context) []entity.Comment
}

Setelah membuat repository yang berisi interface CommentRepository
, selanjutnya kita akan membuat Repository Implementation nya. Buatlah file baru di dalam package repository dengan nama comment_repository_impl.go
dan isikan baris kode berikut
package repository
import (
"belajar-golang-database/entity"
"context"
"database/sql"
"errors"
"strconv"
)
type commentRepositoryImpl struct {
DB *sql.DB
}
func NewCommentRepository(db *sql.DB) CommentRepository {
return &commentRepositoryImpl{DB: db}
}
func (repository *commentRepositoryImpl) Insert(ctx context.Context, comment entity.Comment) (entity.Comment, error) {
query := "INSERT INTO comments(email, comment) VALUES(?, ?)"
result, err := repository.DB.ExecContext(ctx, query, comment.Email, comment.Comment)
if err != nil {
return comment, err
}
id, err := result.LastInsertId()
if err != nil {
return comment, err
}
comment.Id = int32(id)
return comment, nil
}
func (repository *commentRepositoryImpl) FindById(ctx context.Context, id int32) (entity.Comment, error) {
query := "SELECT id, email, comment FROM comments WHERE id = ? LIMIT 1"
rows, err := repository.DB.QueryContext(ctx, query, id)
comment := entity.Comment{}
if err != nil {
return comment, err
}
defer rows.Close()
if rows.Next() {
// ada
rows.Scan(&comment.Id, &comment.Email, &comment.Comment)
return comment, nil
} else {
// tidak ada
return comment, errors.New("Id " + strconv.Itoa(int(id)) + " Not Found")
}
}
func (repository *commentRepositoryImpl) FindAll(ctx context.Context) ([]entity.Comment, error) {
query := "SELECT id, email, comment FROM comments"
rows, err := repository.DB.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var comments []entity.Comment
for rows.Next() {
comment := entity.Comment{}
rows.Scan(&comment.Id, &comment.Email, &comment.Comment)
comments = append(comments, comment)
}
return comments, nil
}

Pada kode program diatas kita membuat struct commentRepositoryImpl
yang berisi DB dan juga kita membuat struct method Insert, FindById, FindAll
yang mengikuti kontrak interface CommentRepository
. Dan terakhir kita membuat Function NewCommentRepository
yang me return interface CommentRepository
, func ini lah yang nanti akan kita gunakan.
Setelah membuat implementasinya, untuk memastikan implementasi sudah berjalan dengan baik maka buatlah file baru pada package repository dengan nama comment_repository_impl_test.go
. Dan masukkan kode program berikut ini
package repository
import (
belajar_golang_database "belajar-golang-database"
"belajar-golang-database/entity"
"context"
"fmt"
"testing"
_ "github.com/go-sql-driver/mysql"
)
func TestCommentInsert(t *testing.T) {
commentRepository := NewCommentRepository(belajar_golang_database.GetConnection())
ctx := context.Background()
comment := entity.Comment{
Email: "repository@rendy.dev",
Comment: "Test Repository",
}
result, err := commentRepository.Insert(ctx, comment)
if err != nil {
panic(err)
}
fmt.Println(result)
}
func TestFindById(t *testing.T) {
commentRepository := NewCommentRepository(belajar_golang_database.GetConnection())
ctx := context.Background()
result, err := commentRepository.FindById(ctx, 1)
if err != nil {
panic(err)
}
fmt.Println(result)
}
func TestFindAll(t *testing.T) {
commentRepository := NewCommentRepository(belajar_golang_database.GetConnection())
ctx := context.Background()
comments, err := commentRepository.FindAll(ctx)
if err != nil {
panic(err)
}
for _, comment := range comments {
fmt.Println("==================")
fmt.Println(comment.Id)
fmt.Println(comment.Email)
fmt.Println(comment.Comment)
}
}

Pada kode proram diatas, kita telah menggunakan Repository pada saat ingin berkomunikasi dengan database, yang mana dengan seperti ini kita bisa membuat busines logic kita menjadi bersih dan untuk SQL akan terpusat pada repository dan tidak tersebar di mana-mana.
Penutup
Pada artikel kali ini kita telah belajar tentang database pada bahasa pemrogaman go. Dan pada artikel selanjutnya saya akan membahas tentang Embed pada bahasa pemrogaman go.