Skip to content

GORM — 全功能 ORM

GORM 是 Go 最流行的 ORM 库,功能完整,支持 MySQL、PostgreSQL、SQLite、SQL Server。

安装

bash
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres  # 或 mysql / sqlite

连接数据库

go
import (
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

func NewDB(dsn string) (*gorm.DB, error) {
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
        NamingStrategy: schema.NamingStrategy{
            TablePrefix:   "app_",   // 表名前缀
            SingularTable: false,    // 使用复数表名
        },
        PrepareStmt: true,  // 缓存预编译语句,提升性能
    })
    if err != nil {
        return nil, err
    }

    // 配置连接池
    sqlDB, _ := db.DB()
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)
    sqlDB.SetConnMaxLifetime(time.Hour)

    return db, nil
}

模型定义

go
import "gorm.io/gorm"

// gorm.Model 包含 ID, CreatedAt, UpdatedAt, DeletedAt
type User struct {
    gorm.Model
    Name     string `gorm:"size:100;not null"`
    Email    string `gorm:"uniqueIndex;size:200;not null"`
    Age      int    `gorm:"check:age >= 0"`
    Role     string `gorm:"default:user;size:20"`
    Active   bool   `gorm:"default:true"`

    // 关联
    Profile  Profile   `gorm:"foreignKey:UserID"`
    Orders   []Order   `gorm:"foreignKey:UserID"`
}

type Profile struct {
    ID     uint   `gorm:"primaryKey"`
    UserID uint   `gorm:"uniqueIndex"`
    Bio    string `gorm:"type:text"`
    Avatar string `gorm:"size:500"`
}

type Order struct {
    gorm.Model
    UserID uint    `gorm:"index;not null"`
    Amount float64 `gorm:"type:decimal(10,2)"`
    Status string  `gorm:"size:20;default:pending"`
    Items  []OrderItem `gorm:"foreignKey:OrderID"`
}

// 自动迁移
db.AutoMigrate(&User{}, &Profile{}, &Order{})

CRUD 操作

创建

go
// 创建单条记录
user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
result := db.Create(&user)
fmt.Println(user.ID)        // 自动填充 ID
fmt.Println(result.Error)   // 错误
fmt.Println(result.RowsAffected)

// 批量创建
users := []User{
    {Name: "Bob", Email: "bob@example.com"},
    {Name: "Charlie", Email: "charlie@example.com"},
}
db.CreateInBatches(users, 100)  // 每批 100 条

// 指定字段创建
db.Select("Name", "Email").Create(&user)

// 忽略某些字段
db.Omit("Age", "Role").Create(&user)

查询

go
// 查询单条
var user User
db.First(&user, 1)                          // 按主键
db.First(&user, "email = ?", "alice@example.com")
db.Where("name = ?", "Alice").First(&user)

// 查询多条
var users []User
db.Find(&users)                             // 全部
db.Where("age > ?", 18).Find(&users)
db.Where("role IN ?", []string{"admin", "mod"}).Find(&users)

// 链式查询
db.Where("active = ?", true).
    Where("age BETWEEN ? AND ?", 18, 60).
    Order("created_at DESC").
    Limit(10).
    Offset(20).
    Find(&users)

// 选择字段
db.Select("id", "name", "email").Find(&users)

// 扫描到自定义结构
type UserSummary struct {
    ID    uint
    Name  string
    Email string
}
var summaries []UserSummary
db.Model(&User{}).Select("id, name, email").Scan(&summaries)

// 计数
var count int64
db.Model(&User{}).Where("active = ?", true).Count(&count)

// 分页
func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
    return func(db *gorm.DB) *gorm.DB {
        offset := (page - 1) * pageSize
        return db.Offset(offset).Limit(pageSize)
    }
}
db.Scopes(Paginate(2, 10)).Find(&users)

更新

go
// 更新单个字段
db.Model(&user).Update("name", "Alice Updated")

// 更新多个字段(struct 只更新非零值字段)
db.Model(&user).Updates(User{Name: "Alice", Age: 31})

// 更新多个字段(map 更新所有指定字段,包括零值)
db.Model(&user).Updates(map[string]interface{}{
    "name": "Alice",
    "age":  0,       // 会更新为 0
    "active": false,
})

// 条件更新
db.Model(&User{}).
    Where("active = ?", false).
    Update("deleted_at", time.Now())

// 使用表达式
db.Model(&user).Update("age", gorm.Expr("age + ?", 1))

删除

go
// 软删除(模型有 DeletedAt 字段时)
db.Delete(&user, 1)
db.Delete(&User{}, "age < ?", 18)

// 查询时默认过滤软删除记录
db.Find(&users)  // 不包含已删除

// 包含软删除记录
db.Unscoped().Find(&users)

// 硬删除
db.Unscoped().Delete(&user)

关联操作

go
// 预加载(避免 N+1 问题)
var users []User
db.Preload("Profile").Find(&users)
db.Preload("Orders").Preload("Orders.Items").Find(&users)

// 条件预加载
db.Preload("Orders", "status = ?", "completed").Find(&users)

// 嵌套预加载
db.Preload("Orders.Items.Product").Find(&users)

// 创建关联
user := User{
    Name: "Alice",
    Profile: Profile{Bio: "Go developer"},
    Orders: []Order{
        {Amount: 99.99, Status: "pending"},
    },
}
db.Create(&user)

// 添加关联
db.Model(&user).Association("Orders").Append(&Order{Amount: 50})

// 查询关联
var orders []Order
db.Model(&user).Association("Orders").Find(&orders)

// 统计关联数量
count := db.Model(&user).Association("Orders").Count()

事务

go
// 自动事务
err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&user).Error; err != nil {
        return err  // 自动回滚
    }
    if err := tx.Create(&profile).Error; err != nil {
        return err  // 自动回滚
    }
    return nil  // 自动提交
})

// 手动事务
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
}

tx.Commit()

// 嵌套事务(使用 SavePoint)
db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user)

    tx.Transaction(func(tx2 *gorm.DB) error {
        tx2.Create(&order)
        return errors.New("回滚内层事务")  // 只回滚内层
    })

    return nil  // 外层提交
})

Hooks(钩子)

go
type User struct {
    gorm.Model
    Password string `gorm:"-"`  // 不存储原始密码
    PassHash string
}

// 创建前加密密码
func (u *User) BeforeCreate(tx *gorm.DB) error {
    hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), 12)
    if err != nil {
        return err
    }
    u.PassHash = string(hash)
    return nil
}

// 更新前验证
func (u *User) BeforeUpdate(tx *gorm.DB) error {
    if u.Age < 0 {
        return errors.New("年龄不能为负数")
    }
    return nil
}

// 可用的 Hooks:
// BeforeCreate, AfterCreate
// BeforeUpdate, AfterUpdate
// BeforeDelete, AfterDelete
// BeforeFind, AfterFind

原生 SQL

go
// 原生查询
var users []User
db.Raw("SELECT * FROM users WHERE age > ? AND active = ?", 18, true).Scan(&users)

// 原生执行
db.Exec("UPDATE users SET active = ? WHERE last_login < ?", false, cutoff)

// 命名参数
db.Raw("SELECT * FROM users WHERE name = @name AND age = @age",
    sql.Named("name", "Alice"),
    sql.Named("age", 30),
).Scan(&users)

性能建议

  • 使用 Select 只查询需要的字段
  • 批量操作用 CreateInBatches
  • 开启 PrepareStmt: true 缓存预编译语句
  • 避免在循环中查询(N+1 问题),用 PreloadJoins
  • 生产环境关闭 SQL 日志或设置为 Warn 级别

本站内容由 褚成志 整理编写,仅供学习参考