Skip to content
← All Skills
🔮

GORM

Data & MessagingOfficial Site ›

Go ORM library — database operations, migrations, associations, query builder

What I Can Do

  • สร้าง data access layer ด้วย GORM สำหรับ Go applications
  • ออกแบบ model structs พร้อม associations (HasOne, HasMany, BelongsTo, Many2Many)
  • จัดการ database migrations ด้วย AutoMigrate และ migration tools
  • เขียน complex queries ด้วย query builder + raw SQL เมื่อจำเป็น
  • Optimize database performance — preloading, indexing, connection pooling

Commands I Use Daily

bash
# install GORM + PostgreSQL driver
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres

# install MySQL driver
go get -u gorm.io/driver/mysql

# install SQLite driver (สำหรับ testing)
go get -u gorm.io/driver/sqlite

# run migrations
go run ./cmd/migrate

# generate models จาก existing database
go install gorm.io/gen/tools/gentool@latest

Database Connection

gorm.Open(postgres.Open(dsn), &gorm.Config{}) สร้าง connection — ตั้ง connection pool ด้วย sqlDB, _ := db.DB() แล้ว sqlDB.SetMaxOpenConns(25), SetMaxIdleConns(10), SetConnMaxLifetime(5 * time.Minute) ป้องกัน connection exhaustion

Model Definition

ใช้ struct เป็น model — gorm.Model embedded struct ให้ ID, CreatedAt, UpdatedAt, DeletedAt อัตโนมัติ, customize ด้วย struct tags gorm:"column:name;type:varchar(100);not null;uniqueIndex", custom primary key ด้วย gorm:"primaryKey"

CRUD Operations

  • Create: db.Create(&user) — insert single record, db.CreateInBatches(users, 100) — batch insert
  • Read: db.First(&user, 1) — find by primary key, db.Find(&users) — find all
  • Update: db.Save(&user) — update all fields, db.Model(&user).Update("name", "new") — update specific field
  • Delete: db.Delete(&user, 1) — soft delete (ถ้ามี DeletedAt), db.Unscoped().Delete(&user, 1) — hard delete

Query Builder

Chain conditions ได้: db.Where("age > ?", 18).Order("name ASC").Limit(10).Offset(0).Find(&users) — ใช้ db.Select("name, email") เลือกเฉพาะ columns, db.Or("role = ?", "admin") เพิ่ม OR condition, db.Not("status = ?", "banned") สำหรับ NOT

Associations

  • BelongsTo: type Order struct { UserID uint; User User } — foreign key อยู่ฝั่ง child
  • HasOne: type User struct { Profile Profile } — inverse ของ BelongsTo
  • HasMany: type User struct { Orders []Order } — one-to-many
  • Many2Many: type User struct { Roles []Role \gorm:"many2many:user_roles"` }` — ผ่าน join table

Preloading (Eager Loading)

db.Preload("Orders").Find(&users) — load associations ล่วงหน้าแทน lazy load, nested preload db.Preload("Orders.Items").Find(&users), conditional preload db.Preload("Orders", "amount > ?", 100).Find(&users), ใช้ db.Joins("Profile") สำหรับ inner join preload (เร็วกว่า Preload สำหรับ belongs_to)

AutoMigrate

db.AutoMigrate(&User{}, &Order{}) — สร้าง/แก้ tables ตาม model structs, เพิ่ม columns และ indexes ได้ แต่ ไม่ลบ columns เก่าและ ไม่เปลี่ยน column types เหมาะสำหรับ development, production ควรใช้ migration tools เช่น golang-migrate หรือ goose

Transactions

ใช้ db.Transaction(func(tx *gorm.DB) error { ... }) — auto rollback เมื่อ return error หรือ panic, auto commit เมื่อ return nil สำหรับ manual control: tx := db.Begin(), tx.Commit(), tx.Rollback() รองรับ nested transactions ด้วย tx.SavePoint("sp1"), tx.RollbackTo("sp1")

Hooks (Callbacks)

Lifecycle hooks ที่ run อัตโนมัติ: BeforeCreate, AfterCreate, BeforeUpdate, AfterUpdate, BeforeSave, AfterSave, BeforeDelete, AfterDelete — implement เป็น method บน model struct เช่น func (u *User) BeforeCreate(tx *gorm.DB) error { ... } ใช้สำหรับ validation, hash password, audit logging

Soft Delete

gorm.Model มี DeletedAt gorm.DeletedAt field — db.Delete(&user) set DeletedAt แทนลบจริง, ทุก query จะ filter WHERE deleted_at IS NULL อัตโนมัติ, ใช้ db.Unscoped().Find(&users) ดู records ที่ถูก soft delete, db.Unscoped().Delete(&user) ลบจริง

Scopes

Reusable query conditions: func Active(db *gorm.DB) *gorm.DB { return db.Where("active = ?", true) } ใช้ db.Scopes(Active, Paginate(page, limit)).Find(&users) — compose หลาย scopes เข้าด้วยกัน ลด code ซ้ำ

Raw SQL & SQL Builder

เมื่อ query builder ไม่พอ: db.Raw("SELECT * FROM users WHERE age > @age", sql.Named("age", 18)).Scan(&users), db.Exec("UPDATE users SET active = ? WHERE last_login < ?", false, cutoff) — ใช้ parameterized queries เสมอ ป้องกัน SQL injection

Performance Tips

  • ใช้ db.Select("id, name") เลือกเฉพาะ columns ที่ต้องการ — ลด data transfer
  • db.CreateInBatches(records, 100) สำหรับ bulk insert — เร็วกว่า create ทีละตัว
  • เปิด PrepareStmt mode: gorm.Config{PrepareStmt: true} — cache prepared statements
  • ใส่ index ด้วย struct tags: gorm:"index", gorm:"uniqueIndex", composite index gorm:"index:idx_name_email,priority:1"

Logger & Debug

db.Debug() แสดง SQL ที่ generate ออก console — ใช้ตอน develop, custom logger ด้วย gorm.Config{Logger: logger.Default.LogMode(logger.Info)}, log slow queries logger.New(log, logger.Config{SlowThreshold: 200 * time.Millisecond}), production ใช้ logger.Silent

ปัญหาที่เจอบ่อย & วิธีแก้

N+1 Query Problem — query ยิงเป็นร้อยโดยไม่รู้ตัว

Loop ผ่าน users แล้ว access user.Orders ทีละคน ทำให้ยิง query เป็น N+1 ครั้ง

สาเหตุ: GORM ใช้ lazy loading — access association จะยิง query ทุกครั้ง ถ้ามี 100 users จะยิง 1 (ดึง users) + 100 (ดึง orders ทีละ user) = 101 queries

วิธีแก้:

  • ใช้ db.Preload("Orders").Find(&users) — load orders ทั้งหมดใน 2 queries (1 ดึง users, 1 ดึง orders ทั้งหมด)
  • ใช้ db.Joins("Profile") สำหรับ belongs_to — ดึงใน query เดียวด้วย JOIN
  • เปิด db.Debug() ตรวจจำนวน queries ที่ยิงจริง

Silent errors — GORM ไม่ return error ทั้งที่ query ผิด

Update/Delete ไม่ error แต่ไม่มี rows ถูกแก้ หรือ Find ไม่เจอแต่ไม่ error

สาเหตุ: db.Find(&users) return empty slice ถ้าไม่เจอ — ไม่ถือเป็น error, db.Model(&user).Update(...) ไม่ error ถ้า WHERE ไม่ match, ต่างจาก db.First() ที่ return gorm.ErrRecordNotFound

วิธีแก้:

  • ตรวจ result.Error ทุกครั้งหลัง query
  • ตรวจ result.RowsAffected สำหรับ update/delete — ถ้าเป็น 0 แปลว่าไม่มี rows ถูกแก้
  • ใช้ db.First() เมื่อ expect ว่าต้องเจอ 1 record — จะ error ถ้าไม่เจอ
  • ใช้ db.Find() เมื่อ empty result เป็นเรื่องปกติ

AutoMigrate เปลี่ยน schema โดยไม่คาดคิด

Schema จริงใน database ไม่ตรงกับ model struct

สาเหตุ: AutoMigrate เพิ่ม columns/tables/indexes ได้ แต่ ไม่ลบ columns เก่า และ ไม่เปลี่ยน column types — ถ้าเปลี่ยนชื่อ field ใน struct จะได้ column ใหม่แต่ column เก่ายังอยู่

วิธีแก้:

  • ใช้ AutoMigrate เฉพาะ development/prototyping
  • Production ใช้ migration tools (golang-migrate, goose) ที่ version control schema changes
  • ทำ migration review ก่อน deploy — ตรวจว่า migration SQL ทำในสิ่งที่ต้องการ
  • เปรียบเทียบ schema จริงกับ model เป็นระยะ

Soft Delete ทำให้ records "หาย" แต่ยังอยู่ใน DB

ลบ record แล้วแต่ unique constraint ยัง conflict หรือ count ไม่ตรง

สาเหตุ: gorm.Model มี DeletedAt field ที่ทำ soft delete อัตโนมัติ — ทุก query เพิ่ม WHERE deleted_at IS NULL โดยอัตโนมัติ, unique constraints ไม่ work ตามที่คิดเพราะ soft-deleted records ยังมี data อยู่ใน table

วิธีแก้:

  • ใช้ db.Unscoped() เมื่อต้องการเห็นทุก records รวม soft-deleted
  • ถ้าไม่ต้องการ soft delete: สร้าง base model เองโดยไม่มี DeletedAt แทนใช้ gorm.Model
  • สำหรับ unique constraints: ใช้ partial unique index ที่ exclude soft-deleted records CREATE UNIQUE INDEX ... WHERE deleted_at IS NULL

Transaction ไม่ rollback เมื่อ error

บาง operation ใน transaction สำเร็จ บางตัวไม่ — data ไม่ consistent

สาเหตุ: ใช้ manual transaction (db.Begin()) แล้วลืม rollback เมื่อ error, return ก่อน commit/rollback, หรือ panic ใน transaction block ทำให้ transaction ค้าง

วิธีแก้:

  • ใช้ db.Transaction(func(tx *gorm.DB) error { ... }) แทน manual — auto rollback เมื่อ return error หรือ panic
  • ถ้าใช้ manual: tx := db.Begin() แล้ว defer tx.Rollback() ทันที — commit จะ override rollback
  • ใช้ tx (ไม่ใช่ db) สำหรับทุก operation ภายใน transaction — ถ้าใช้ db จะอยู่นอก transaction

Query ช้าเพราะ missing indexes

Query ที่ควรเร็วกลับใช้เวลาหลายวินาที

สาเหตุ: GORM ไม่สร้าง indexes อัตโนมัติ (ยกเว้น primary key) — query ที่ filter/sort บน columns ที่ไม่มี index ทำ sequential scan ทั้ง table

วิธีแก้:

  • ใส่ gorm:"index" ใน struct tags สำหรับ columns ที่ query บ่อย
  • ใช้ composite index: gorm:"index:idx_user_status,priority:1" สำหรับ queries ที่ filter หลาย columns
  • ตรวจ slow queries ด้วย GORM logger: SlowThreshold: 200 * time.Millisecond
  • ใช้ EXPLAIN ANALYZE ตรวจ query plan โดยตรง — ดูว่า query ใช้ index หรือ sequential scan

Related Skills

  • Go — ภาษาที่ GORM สร้างขึ้นมา
  • PostgreSQL — database ที่ใช้คู่กับ GORM บ่อยที่สุด
  • SQL — ภาษา query ที่ GORM generate
  • Gin — web framework ที่ใช้คู่กับ GORM เป็น data layer