Associations, Migrations & Transactions
Associations & Preload
type User struct {
gorm.Model
Orders []Order
}
type Order struct {
gorm.Model
UserID uint
}
db.Preload("Orders").Find(&users) // one extra query, not N extra queriesGORM infers a has-many relationship from struct shape and naming (UserID on Order + []Order on User). Without Preload, accessing .Orders per user in a loop would issue one query per user — the classic N+1 query problem. Preload eagerly fetches the association in one additional query instead.
AutoMigrate vs. Hand-Written Migrations
db.AutoMigrate(&User{}, &Order{})AutoMigrate creates or updates tables/columns/indexes to match struct definitions, without dropping existing data — convenient for development, but additive by design: it won't drop unused columns or safely handle renames. Production codebases often pair GORM with a dedicated migration tool for changes AutoMigrate can't safely express.
Transactions & Hooks
db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&order).Error; err != nil {
return err // rolls back automatically
}
return tx.Model(&account).Update("balance", newBalance).Error
})
func (u *User) BeforeCreate(tx *gorm.DB) error {
u.ID = uuid.New()
return nil
}Transaction commits automatically if the function returns nil and rolls back on error — the standard way to keep multi-step operations atomic. Hooks like BeforeCreate/AfterUpdate run automatically at specific points in a model's persistence lifecycle, useful for defaulting fields or validation tied to the operation.
Raw SQL Escape Hatch
Raw/Exec let complex or database-specific SQL bypass the chainable DSL while still benefiting from GORM's connection pool and struct scanning — used when a query doesn't map cleanly onto the query builder.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free