GORM
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
# 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@latestDatabase 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 ทีละตัว- เปิด
PrepareStmtmode:gorm.Config{PrepareStmt: true}— cache prepared statements - ใส่ index ด้วย struct tags:
gorm:"index",gorm:"uniqueIndex", composite indexgorm:"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