GORM
02 / 02

Associations, Migrations & Transactions

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 queries

GORM 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