GORM Database Operations in Golang: Advanced Patterns
Master GORM for database operations in Golang, including associations, transactions, migrations, query optimization, and advanced query patterns.

GORM Database Operations in Golang: Advanced Patterns
GORM is the most popular ORM for Golang, providing a developer-friendly way to interact with databases. This article explores advanced GORM patterns, including associations, transactions, migrations, and performance optimization.
Getting Started
Installation
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
# or
go get -u gorm.io/driver/mysql
go get -u gorm.io/driver/sqliteBasic Setup
package main
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func initDB() (*gorm.DB, error) {
dsn := "host=localhost user=postgres password=postgres dbname=mydb port=5432 sslmode=disable TimeZone=Asia/Shanghai"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
return nil, err
}
return db, nil
}Model Definition
Basic Models
package models
import (
"time"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
FirstName string `gorm:"type:varchar(100);not null" json:"first_name"`
LastName string `gorm:"type:varchar(100);not null" json:"last_name"`
Email string `gorm:"type:varchar(255);uniqueIndex;not null" json:"email"`
Age int `gorm:"type:integer" json:"age"`
// Relationships
Orders []Order `gorm:"foreignKey:UserID" json:"orders"`
Profile Profile `gorm:"foreignKey:UserID" json:"profile"`
}
type Order struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
UserID uint `gorm:"not null;index" json:"user_id"`
User User `gorm:"foreignKey:UserID" json:"user"`
Product string `gorm:"type:varchar(255)" json:"product"`
Amount float64 `gorm:"type:decimal(10,2)" json:"amount"`
Status string `gorm:"type:varchar(50);default:'pending'" json:"status"`
OrderItems []OrderItem `gorm:"foreignKey:OrderID" json:"order_items"`
}
type Profile struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `gorm:"uniqueIndex;not null" json:"user_id"`
User User `gorm:"foreignKey:UserID" json:"user"`
Bio string `gorm:"type:text" json:"bio"`
Avatar string `gorm:"type:varchar(255)" json:"avatar"`
}CRUD Operations
Create
// Single create
user := User{
FirstName: "John",
LastName: "Doe",
Email: "john@example.com",
Age: 30,
}
result := db.Create(&user)
// Batch create
users := []User{
{FirstName: "Jane", LastName: "Smith", Email: "jane@example.com"},
{FirstName: "Bob", LastName: "Johnson", Email: "bob@example.com"},
}
db.Create(&users)
// Create with selected fields
db.Select("FirstName", "LastName", "Email").Create(&user)
// Create with omitted fields
db.Omit("Age").Create(&user)Read
// Find by ID
var user User
db.First(&user, 1) // WHERE id = 1
db.First(&user, "id = ?", 1)
// Find all
var users []User
db.Find(&users)
// Conditions
db.Where("age > ?", 18).Find(&users)
db.Where("first_name = ? AND age > ?", "John", 18).Find(&users)
// Or conditions
db.Where("first_name = ?", "John").Or("last_name = ?", "Doe").Find(&users)
// Not conditions
db.Not("age = ?", 18).Find(&users)
// In
db.Where("age IN ?", []int{18, 20, 22}).Find(&users)
// Like
db.Where("email LIKE ?", "%@example.com").Find(&users)
// Order
db.Order("age desc").Find(&users)
db.Order("age desc, first_name asc").Find(&users)
// Limit and Offset
db.Limit(10).Offset(20).Find(&users)
// Count
var count int64
db.Model(&User{}).Where("age > ?", 18).Count(&count)Update
// Update single field
db.Model(&user).Update("age", 31)
// Update multiple fields
db.Model(&user).Updates(User{Age: 31, Email: "newemail@example.com"})
// Update with map
db.Model(&user).Updates(map[string]interface{}{
"age": 31,
"email": "newemail@example.com",
})
// Update selected fields
db.Model(&user).Select("age").Updates(User{Age: 31, Email: "ignored"})
// Update without hooks
db.Model(&user).UpdateColumn("age", 31)
// Batch update
db.Model(&User{}).Where("age < ?", 18).Update("age", 18)Delete
// Soft delete
db.Delete(&user)
// Hard delete
db.Unscoped().Delete(&user)
// Delete by ID
db.Delete(&User{}, 1)
// Batch delete
db.Where("age < ?", 18).Delete(&User{})Associations
Belongs To
type Order struct {
UserID uint
User User `gorm:"foreignKey:UserID"`
}
// Preload
var order Order
db.Preload("User").First(&order, 1)
// Eager loading
db.Preload("User.Profile").First(&order, 1)Has One
type User struct {
Profile Profile `gorm:"foreignKey:UserID"`
}
// Preload
var user User
db.Preload("Profile").First(&user, 1)Has Many
type User struct {
Orders []Order `gorm:"foreignKey:UserID"`
}
// Preload
var user User
db.Preload("Orders").First(&user, 1)
// Preload with conditions
db.Preload("Orders", "status = ?", "completed").First(&user, 1)Many to Many
type User struct {
Roles []Role `gorm:"many2many:user_roles;"`
}
type Role struct {
ID uint
Name string
Users []User `gorm:"many2many:user_roles;"`
}
// Create with associations
user := User{
FirstName: "John",
Roles: []Role{
{Name: "admin"},
{Name: "user"},
},
}
db.Create(&user)
// Append associations
var user User
var role Role
db.First(&user, 1)
db.First(&role, 1)
db.Model(&user).Association("Roles").Append(&role)
// Replace associations
db.Model(&user).Association("Roles").Replace(&roles)
// Delete associations
db.Model(&user).Association("Roles").Delete(&role)
// Clear associations
db.Model(&user).Association("Roles").Clear()
// Count associations
count := db.Model(&user).Association("Roles").Count()Transactions
Basic Transaction
// Manual transaction
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Create(&user).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Create(&order).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().ErrorTransaction Function
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&user).Error; err != nil {
return err
}
if err := tx.Create(&order).Error; err != nil {
return err
}
return nil
})Nested Transactions
db.Transaction(func(tx1 *gorm.DB) error {
tx1.Create(&user)
tx1.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&order)
return nil
})
return nil
})Migrations
Auto Migrate
// Auto migrate
err := db.AutoMigrate(&User{}, &Order{}, &Profile{})
// Auto migrate with options
err := db.AutoMigrate(&User{}, &Order{})Manual Migrations
// Create table
db.Exec(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`)
// Add column
db.Exec("ALTER TABLE users ADD COLUMN age INTEGER")
// Drop column
db.Exec("ALTER TABLE users DROP COLUMN age")Query Optimization
Select Specific Fields
// Select only needed fields
db.Select("id", "first_name", "email").Find(&users)
// Omit fields
db.Omit("created_at", "updated_at").Find(&users)Preloading Optimization
// Preload with select
db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
return db.Select("id", "user_id", "amount")
}).Find(&users)
// Preload with conditions
db.Preload("Orders", "status = ?", "completed").Find(&users)Joins
// Inner join
db.Joins("User").Find(&orders)
// Left join
db.Joins("LEFT JOIN users ON orders.user_id = users.id").Find(&orders)
// Join with conditions
db.Joins("User").Where("users.age > ?", 18).Find(&orders)Raw Queries
// Raw SQL
var users []User
db.Raw("SELECT * FROM users WHERE age > ?", 18).Scan(&users)
// Exec raw SQL
db.Exec("UPDATE users SET age = ? WHERE id = ?", 31, 1)
// Named arguments
db.Raw("SELECT * FROM users WHERE age > @age", sql.Named("age", 18)).Scan(&users)Hooks
Model Hooks
type User struct {
// ... fields
}
// BeforeCreate hook
func (u *User) BeforeCreate(tx *gorm.DB) error {
// Hash password, set defaults, etc.
return nil
}
// AfterCreate hook
func (u *User) AfterCreate(tx *gorm.DB) error {
// Send welcome email, etc.
return nil
}
// BeforeUpdate hook
func (u *User) BeforeUpdate(tx *gorm.DB) error {
// Update timestamps, etc.
return nil
}Scopes
Define Scopes
// Scope function
func AdultUsers(db *gorm.DB) *gorm.DB {
return db.Where("age >= ?", 18)
}
func ActiveUsers(db *gorm.DB) *gorm.DB {
return db.Where("deleted_at IS NULL")
}
// Use scopes
db.Scopes(AdultUsers, ActiveUsers).Find(&users)
// Chain scopes
db.Scopes(AdultUsers).Scopes(ActiveUsers).Find(&users)Best Practices
- Use Transactions: For related operations
- Optimize Queries: Select only needed fields
- Use Indexes: For frequently queried fields
- Batch Operations: For bulk inserts/updates
- Connection Pooling: Configure properly
- Error Handling: Always check errors
- Use Preload: Instead of N+1 queries
Conclusion
GORM provides powerful features for database operations in Golang. By mastering associations, transactions, migrations, and query optimization, you can build efficient, maintainable database layers. Remember to use transactions for related operations, optimize queries, and leverage GORM's hooks and scopes for cleaner code.
References
Want more insights?
Subscribe to our newsletter or follow us for more updates on software development and team scaling.
Contact Us