Kode program : Eksekusi Perintah SQL

Golang – Pengenalan dan penggunaan database MYSQL

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

Import database mysql
Import database mysql

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()
}
Kode program membuat koneksi ke database
Kode program membuat koneksi ke database

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

Output koneksi ke database
Output koneksi ke database

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

MethodKeterangan
(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
Pengaturan Database Pooling

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
}
Function GetConnection
Function GetConnection

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;

Detail table customer
Detail table 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")
}
Kode program : Eksekusi Perintah SQL
Kode program : Eksekusi Perintah SQL

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)
	}
}
Kode program : Query SQL
Kode program : Query SQL

Coba jalankan kode program diatas, dan perhatikan outputnya

Output Query SQL
Output Query SQL

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.

Detail table customer
Detail table customer

Mapping tipe data

Tipe Data DatabaseTipe Data Golang
VARCHAR, CHARstring
INT, BIGINTint32, int64
FLOAT, DOUBLEfloat32, float64
BOOLEANbool
DATE, DATETIME, TIME, TIMESTAMPtime.Time
Mapping tipe data

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")
}
Query insert customer
Query insert 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)
	}
}
Kode : Query SQL pada golang
Kode : Query SQL pada golang

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

Error tipe data date
Error tipe data date

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
}
Kode program : GetConnection
Kode program : GetConnection

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

Output Query SQL Complex
Output Query SQL Complex

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 GolangTipe Data Nullable
stringdatabase/sql.NullString
booldatabase/sql.NullBool
float64database/sql.NullFloat64
int32database/sql.NullInt32
int64database/sql.NullInt64
time.Timedatabase/sql.NullTime
Tipe Data Nullable

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)
}
Kode : Mengambil data dari tipe data nullable
Kode : Mengambil data dari tipe data nullable

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

Contoh SQL
Contoh SQL

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")
}
Kode program : Query with parameter
Kode program : Query with parameter

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

Detail table comments
Detail table 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)
}
Kode program : Insert auto increment
Kode program : Insert auto increment

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.

Output kode program auto increment
Output kode program auto increment

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)
	}
}
Kode program : Prepare statement
Kode program : Prepare statement

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

Output prepare statement
Output prepare statement

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)
	}
}
Kode program : Databases transactions
Kode program : Databases transactions

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
}
Kode progrem : Entity comment
Kode progrem : Entity comment

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
}
Kode program : Comment repository
Kode program : Comment repository

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
}
Kode program : Repository implementation
Kode program : Repository implementation

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)
	}
}
Kode program : Unit test repository
Kode program : Unit test repository

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.

Leave a reply:

Your email address will not be published.

Site Footer

Sliding Sidebar

About Me

About Me

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam.

Social Profiles

Facebook