Kodeloka
  1. Kodeloka
  2. Kelas Golang
  3. Materi

Domain 17 · 9 bab

Golang GORM ORM Framework

181. Pengenalan GORM & DB Connection Setup

Memahami konsep Object-Relational Mapping (ORM) dan membuka koneksi database MySQL/PostgreSQL dengan GORM.

Apa itu GORM?

GORM adalah library Object-Relational Mapping (ORM) paling populer untuk Golang. GORM memungkinkan developer berinteraksi dengan database relasional (MySQL, PostgreSQL, SQLite, SQL Server) menggunakan Go Struct tanpa perlu menulis SQL manual secara berulang.

💡 Keunggulan Utama GORM:
  • Auto Migration skema tabel otomatis dari Go Struct.
  • Mendukung relasi lengkap (Has One, Has Many, Belongs To, Many To Many).
  • Fitur Transactions, Nested Transactions, dan SavePoint.
  • Context, Preloading (Eager Loading), dan Callbacks/Hooks.

Instalasi GORM & Driver:

go get gorm.io/gorm
go get gorm.io/driver/mysql  # Driver MySQL
go get gorm.io/driver/postgres # Driver Postgres

Contoh program

package main

import (
	"fmt"
)

// Simulasi Koneksi Database GORM
type GormDB struct {
	Dialect string
	DSN     string
}

func OpenGorm(dialect, dsn string) (*GormDB, error) {
	fmt.Printf("[GORM] Opening connection to %s DB with DSN: %s\n", dialect, dsn)
	return &GormDB{Dialect: dialect, DSN: dsn}, nil
}

func main() {
	fmt.Println("=== INITIALIZING GORM DB CONNECTION ===")
	dsn := "user:pass@tcp(127.0.0.1:3306)/my_db?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := OpenGorm("mysql", dsn)
	if err == nil {
		fmt.Println("Koneksi Database GORM Berhasil Dibuat!")
		_ = db
	}
}

Praktik dan kuis bab ini di kelas interaktif →

182. Defining GORM Models, Struct Tags & AutoMigrate

Mendefinisikan GORM Model, menggunakan gorm.Model base struct, dan menjalankan skema database AutoMigrate.

Mendesain GORM Model & AutoMigrate

Di GORM, tabel database direpresentasikan oleh Struct. GORM menyediakan struct bawaan gorm.Model yang sudah berisi field umum: ID, CreatedAt, UpdatedAt, dan DeletedAt (Soft Delete).

📌 Contoh Definisi Model & Tag:
type Product struct {
    gorm.Model
    Code  string  `gorm:"type:varchar(50);uniqueIndex;not null"`
    Price float64 `gorm:"type:decimal(10,2);default:0"`
}

// Menjalankan Auto Migration (membuat/memperbarui tabel otomatis)
db.AutoMigrate(&Product{})

Contoh program

package main

import (
	"fmt"
	"time"
)

type GormModel struct {
	ID        uint       `json:"id"`
	CreatedAt time.Time  `json:"created_at"`
	UpdatedAt time.Time  `json:"updated_at"`
	DeletedAt *time.Time `json:"deleted_at,omitempty"`
}

type Category struct {
	GormModel
	Name string `json:"name"`
	Slug string `json:"slug"`
}

func main() {
	cat := Category{
		GormModel: GormModel{ID: 1, CreatedAt: time.Now(), UpdatedAt: time.Now()},
		Name:      "Electronics",
		Slug:      "electronics",
	}

	fmt.Println("=== MODEL CATEGORY GORM DEMO ===")
	fmt.Printf("ID: %d | Name: %s | Slug: %s\n", cat.ID, cat.Name, cat.Slug)
	fmt.Println("CreatedAt:", cat.CreatedAt.Format("2006-01-02 15:04:05"))
}

Praktik dan kuis bab ini di kelas interaktif →

183. CRUD Basic Operations: Insert & Single Record Creation

Memasukkan data baru ke database menggunakan db.Create() dan batch insert.

Membuat Record Baru (Insert)

Untuk memasukkan data baru ke database, kita cukup menginstansiasi Struct lalu memanggil db.Create(&object). GORM akan mengisi kembali nilai `ID` auto-increment dan timestamp ke struct tersebut.

💡 Batch Insert (Multiple Records):
users := []User{
    {Name: "Budi", Age: 20},
    {Name: "Siti", Age: 22},
}
result := db.Create(&users) // Menginsert banyak record sekaligus dalam 1 query SQL!
fmt.Println("Jumlah record terinsert:", result.RowsAffected)

Contoh program

package main

import (
	"fmt"
)

type User struct {
	ID   uint
	Name string
	Age  int
}

func main() {
	var usersDB []User
	autoIncID := uint(1)

	// Simulasi db.Create()
	newUser := User{Name: "Eko Kurniawan", Age: 30}
	newUser.ID = autoIncID
	usersDB = append(usersDB, newUser)

	fmt.Println("=== DEMO GORM db.Create() ===")
	fmt.Printf("User Terinsert => ID: %d | Name: %s | Age: %d\n", newUser.ID, newUser.Name, newUser.Age)
	fmt.Println("Total Rows in DB:", len(usersDB))
}

Praktik dan kuis bab ini di kelas interaktif →

184. Querying Records (First, Find, Where, Like, Order, Limit)

Mencari dan memfilter data dari database menggunakan metode First, Find, Where, dan pencarian berantai (chaining).

Membaca Data (Querying)

GORM menyediakan API fluent interface untuk query data secara mudah.

Metode GORM Penjelasan Query SQL Setara
db.First(&user) Mengambil 1 record pertama berdasarkan urutan Primary Key. SELECT * FROM users ORDER BY id LIMIT 1
db.Find(&users) Mengambil seluruh record ke dalam Slice. SELECT * FROM users
db.Where("age > ?", 18) Menambahkan klausa WHERE dengan prepared statement. WHERE age > 18
db.Order("age desc").Limit(5) Pengurutan dan pembatasan jumlah data. ORDER BY age DESC LIMIT 5

Contoh program

package main

import (
	"fmt"
)

type Customer struct {
	ID   int
	Name string
	City string
}

func main() {
	customers := []Customer{
		{ID: 1, Name: "Andi", City: "Jakarta"},
		{ID: 2, Name: "Budi", City: "Bandung"},
		{ID: 3, Name: "Cici", City: "Jakarta"},
	}

	fmt.Println("=== DEMO GORM WHERE FILTER (City = Jakarta) ===")
	for _, c := range customers {
		if c.City == "Jakarta" {
			fmt.Printf("- Customer ID %d: %s (%s)\n", c.ID, c.Name, c.City)
		}
	}
}

Praktik dan kuis bab ini di kelas interaktif →

185. Updates & Deletions (Save, Updates, Delete, Soft Delete)

Mengubah data dengan Save/Updates dan menghapus data dengan Soft Delete (gorm.DeletedAt).

Update & Delete pada GORM

Untuk mengupdate record, kita bisa memilih mengupdate seluruh field (db.Save(&user)) atau hanya field tertentu (db.Model(&user).Updates(User{Name: "New Name"})).

💡 Apa itu Soft Delete?

Jika model memiliki field gorm.DeletedAt, pemanggilan db.Delete(&user) TIDAK akan menghapus baris dari tabel MySQL. GORM hanya akan mengisikan kolom `deleted_at` dengan waktu saat ini.

Query pencarian selanjutnya secara otomatis akan mengabaikan record yang sudah di-soft-delete!

📌 Mengambil Data Terhapus:
db.Unscoped().Where("id = ?", 1).First(&user) // Memaksa membaca data yang ter-soft-delete

Contoh program

package main

import (
	"fmt"
	"time"
)

type SoftDeleteUser struct {
	ID        int
	Name      string
	DeletedAt *time.Time
}

func main() {
	now := time.Now()
	user := SoftDeleteUser{
		ID:        10,
		Name:      "Dewi",
		DeletedAt: &now, // Soft deleted
	}

	fmt.Println("=== DEMO SOFT DELETE GORM ===")
	fmt.Printf("User ID %d (%s)\n", user.ID, user.Name)
	if user.DeletedAt != nil {
		fmt.Println("Status Data: TERHAPUS SECARA SOFT DELETE pada", user.DeletedAt.Format("15:04:05"))
		fmt.Println("Query biasa 'db.Find()' akan otomatis menyembunyikan data ini.")
	}
}

Praktik dan kuis bab ini di kelas interaktif →

186. GORM Relationships: Belongs To & Has One

Membuat dan memetakan relasi 1-to-1 antara dua struct (Belongs To & Has One) menggunakan foreign key.

Relasi 1-to-1 di GORM

GORM mendukung pembuatan relasi database antar tabel secara deklaratif.

  • Belongs To: Struct child menyimpan `ForeignKey` dari struct parent (misal: User `Belongs To` Company).
  • Has One: Struct parent memiliki tepat 1 child (misal: User `Has One` CreditCard).
📌 Definisi Struct Belongs To:
type Company struct {
    ID   int
    Name string
}

type User struct {
    gorm.Model
    Name      string
    CompanyID int     // Foreign Key
    Company   Company // Relasi Belongs To
}

Contoh program

package main

import (
	"fmt"
)

type Company struct {
	ID   int
	Name string
}

type Employee struct {
	ID        int
	Name      string
	CompanyID int
	Company   Company // Belongs To
}

func main() {
	emp := Employee{
		ID:        1,
		Name:      "Rian",
		CompanyID: 101,
		Company:   Company{ID: 101, Name: "PT Tech Indonesia"},
	}

	fmt.Println("=== RELASI BELONGS TO GORM ===")
	fmt.Printf("Karyawan: %s | Perusahaan: %s (FK CompanyID: %d)\n", emp.Name, emp.Company.Name, emp.CompanyID)
}

Praktik dan kuis bab ini di kelas interaktif →

187. GORM Relationships: Has Many & Many to Many

Mengelola relasi 1-to-N dan N-to-N serta menggunakan db.Preload() untuk eager loading data relasi.

Relasi Has Many & Many to Many

Saat satu record memiliki banyak anak (misal: 1 User punya banyak CreditCard atau 1 User punya banyak Role), kita menggunakan relasi Has Many atau Many to Many.

💡 Apa itu Preloading (Eager Loading)?

Secara default, GORM tidak akan mengambil data relasi untuk menghemat memory (Lazy). Untuk mengambil data anak sekaligus saat query, kita WAJIB memanggil db.Preload("NamaFieldRelasi").

var user User
db.Preload("CreditCards").Preload("Roles").First(&user, 1)
// Sekarang user.CreditCards dan user.Roles sudah terisi lengkap!

Contoh program

package main

import (
	"fmt"
)

type CreditCard struct {
	Number string
}

type UserHasMany struct {
	Name        string
	CreditCards []CreditCard // Has Many
}

func main() {
	user := UserHasMany{
		Name: "Budi Santoso",
		CreditCards: []CreditCard{
			{Number: "4567-xxxx-1122"},
			{Number: "5412-xxxx-9900"},
		},
	}

	fmt.Println("=== GORM PRELOAD (EAGER LOADING) SIMULATION ===")
	fmt.Println("User Name:", user.Name)
	fmt.Println("Daftar Kartu Kredit (Preloaded):")
	for i, card := range user.CreditCards {
		fmt.Printf("  %d. %s\n", i+1, card.Number)
	}
}

Praktik dan kuis bab ini di kelas interaktif →

188. GORM Transactions & Hooks (Callbacks)

Menjaga integritas data transaksi bisnis dengan db.Transaction() dan memanfaatkan GORM Lifecycle Hooks.

Database Transaction & Lifecycle Hooks

Database Transaction memastikan sekumpulan operasi SQL berhasil **semuanya** (Commit) atau gagal **semuanya** (Rollback).

📌 Otomatisasi Transaksi dengan db.Transaction:
err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&user).Error; err != nil {
        return err // Otomatis Rollback!
    }
    if err := tx.Create(&account).Error; err != nil {
        return err // Otomatis Rollback!
    }
    return nil // Otomatis Commit!
})

GORM Hooks (Callbacks):

Hooks adalah fungsi yang dipanggil otomatis sebelum/sesudah operasi (misal: BeforeCreate untuk me-hash password).

Contoh program

package main

import (
	"fmt"
)

type Account struct {
	Balance float64
}

func TransferMoney(from, to *Account, amount float64) error {
	if from.Balance < amount {
		return fmt.Errorf("ROLLBACK: Saldo pengirim tidak mencukupi")
	}
	from.Balance -= amount
	to.Balance += amount
	return nil // COMMIT
}

func main() {
	acc1 := &Account{Balance: 500000}
	acc2 := &Account{Balance: 100000}

	fmt.Println("=== SIMULASI TRANSACTION TRANSFER ===")
	err := TransferMoney(acc1, acc2, 200000)
	if err == nil {
		fmt.Println("TRANSAKSI COMMIT BERHASIL!")
		fmt.Printf("Saldo Acc1: %.0f | Saldo Acc2: %.0f\n", acc1.Balance, acc2.Balance)
	}
}

Praktik dan kuis bab ini di kelas interaktif →

189. Advanced GORM: Raw SQL, Exec & Scopes

Mengeksekusi query SQL kustom dengan db.Raw() dan mengisolasi logika query reusable menggunakan Scopes.

Raw SQL & Reusable Scopes

Meskipun menggunakan ORM, terkadang kita butuh mengeksekusi query SQL kompleks secara manual atau menggunakan reusable filter (Scopes).

💡 Memanggil Raw SQL:
db.Raw("SELECT id, name FROM users WHERE age > ?", 20).Scan(&results)
db.Exec("UPDATE users SET status = ? WHERE id = ?", "ACTIVE", 10)
📌 Menggunakan Scopes:
func ActiveUsers(db *gorm.DB) *gorm.DB {
    return db.Where("status = ?", "ACTIVE")
}

// Penggunaan Scopes yang sangat rapi:
db.Scopes(ActiveUsers).Find(&users)

Contoh program

package main

import (
	"fmt"
)

type UserScope struct {
	Name   string
	Status string
}

func FilterActive(users []UserScope) []UserScope {
	var active []UserScope
	for _, u := range users {
		if u.Status == "ACTIVE" {
			active = append(active, u)
		}
	}
	return active
}

func main() {
	list := []UserScope{
		{Name: "Budi", Status: "ACTIVE"},
		{Name: "Joko", Status: "SUSPENDED"},
		{Name: "Siti", Status: "ACTIVE"},
	}

	activeUsers := FilterActive(list)
	fmt.Println("=== DEMO GORM SCOPES (FILTER ACTIVE) ===")
	for _, u := range activeUsers {
		fmt.Println("Active User:", u.Name)
	}
}

Praktik dan kuis bab ini di kelas interaktif →