Skip to content

GORM 数据库操作 ​

简介 ​

GORM 是 Go 的 ORM 库,特点:

  • 全功能 ORM (关联、事务、钩子等)
  • 自动迁移
  • 支持 MySQL、PostgreSQL、SQLite、SQL Server
  • 灵活的插件系统

安装 ​

bash
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql    # MySQL
go get -u gorm.io/driver/postgres # PostgreSQL
go get -u gorm.io/driver/sqlite   # SQLite

快速开始 ​

连接数据库 ​

go
package main

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

func main() {
    dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"

    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
        // 禁用外键约束
        DisableForeignKeyConstraintWhenMigrating: true,
        // 日志级别
        Logger: logger.Default.LogMode(logger.Info),
    })
    if err != nil {
        panic(err)
    }

    // 获取底层 *sql.DB
    sqlDB, _ := db.DB()
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)
    sqlDB.SetConnMaxLifetime(time.Hour)
}

定义模型 ​

go
package model

import (
    "time"
    "gorm.io/gorm"
)

// User 用户模型
type User struct {
    ID        uint           `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`  // 软删除

    Name     string `gorm:"size:255;not null"`
    Email    string `gorm:"size:255;uniqueIndex"`
    Age      int    `gorm:"comment:用户年龄"`
    Birthday *time.Time

    // 关联
    Profile  *Profile
    Orders   []Order
    Roles    []Role `gorm:"many2many:user_roles;"`
}

// Profile 用户资料
type Profile struct {
    ID     uint   `gorm:"primaryKey"`
    UserID uint   `gorm:"uniqueIndex;not null"`
    Bio    string `gorm:"type:text"`
    Avatar string
}

// Order 订单
type Order struct {
    ID        uint   `gorm:"primaryKey"`
    UserID    uint   `gorm:"index"`
    OrderNo   string `gorm:"size:64;uniqueIndex"`
    Amount    int64  `gorm:"comment:金额 (分)"`
    Status    int    `gorm:"default:0"`
    User      User   `gorm:"foreignKey:UserID"`
    Items     []OrderItem
}

// OrderItem 订单项
type OrderItem struct {
    ID      uint   `gorm:"primaryKey"`
    OrderID uint   `gorm:"index"`
    Product string
    Quantity int
    Price   int64
}

// Role 角色
type Role struct {
    ID   uint   `gorm:"primaryKey"`
    Name string `gorm:"size:64;uniqueIndex"`
    Users []User `gorm:"many2many:user_roles;"`
}

自动迁移 ​

go
func migrate(db *gorm.DB) error {
    // 自动迁移所有模型
    return db.AutoMigrate(
        &User{},
        &Profile{},
        &Order{},
        &OrderItem{},
        &Role{},
    )
}

// 自定义迁移
func migrateWithCustom(db *gorm.DB) error {
    err := db.AutoMigrate(&User{})
    if err != nil {
        return err
    }

    // 创建索引
    db.Exec("CREATE INDEX idx_users_email ON users(email)")

    // 添加外键
    db.Exec("ALTER TABLE profiles ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id)")

    return nil
}

CRUD 操作 ​

创建 ​

go
// 单条创建
user := User{Name: "Alice", Email: "alice@example.com", Age: 25}
result := db.Create(&user)
fmt.Println(user.ID)  // 自动生成的 ID

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

// 带关联创建
user := User{
    Name:  "David",
    Email: "david@example.com",
    Profile: &Profile{
        Bio: "Developer",
    },
    Orders: []Order{
        {OrderNo: "ORD001", Amount: 10000},
    },
}
db.Create(&user)  // 级联创建关联数据

// 使用 Map 创建
db.Model(&User{}).Create(map[string]interface{}{
    "Name":  "Eve",
    "Email": "eve@example.com",
})

// 选择字段创建
db.Select("Name", "Email").Create(&User{
    Name:  "Frank",
    Email: "frank@example.com",
    Age:   30,  // 会被忽略
})

// 忽略字段创建
db.Omit("Age").Create(&user)

// 创建或更新 (Upsert)
user := User{ID: 1, Name: "Updated"}
db.Clauses(clause.OnConflict{
    UpdateAll: true,  // 更新所有字段
}).Create(&user)

查询 ​

go
var user User
var users []User

// 根据主键查询
db.First(&user, 1)           // WHERE id = 1
db.Take(&user, "id = ?", 1)  // 第一条

db.Find(&users, []int{1, 2, 3})  // WHERE id IN (1,2,3)

// 条件查询
db.Where("name = ?", "Alice").First(&user)
db.Where("name = ? AND age > ?", "Alice", 20).Find(&users)
db.Where("age IN ?", []int{20, 30}).Find(&users)
db.Where("name LIKE ?", "%al%").Find(&users)

// Map 条件
db.Where(map[string]interface{}{"name": "Alice", "age": 25}).Find(&users)

// 结构体条件 (仅非零值)
db.Where(&User{Name: "Alice"}).Find(&users)

// OR 条件
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)

// NOT 条件
db.Not("name = ?", "Alice").Find(&users)
db.Where(db.Not("id IN ?", []int{1, 2, 3})).Find(&users)

// 选择字段
db.Select("name", "email").Find(&users)
db.Select([]string{"name", "email"}).Find(&users)

// 排除字段
db.Omit("password").Find(&users)

// 排序
db.Order("created_at DESC").Find(&users)
db.Order("age ASC, name DESC").Find(&users)

// 限制和偏移
db.Limit(10).Offset(20).Find(&users)  // LIMIT 10 OFFSET 20

// 计数
var count int64
db.Model(&User{}).Where("age > ?", 20).Count(&count)

// 聚合
db.Model(&Order{}).Select("SUM(amount)").Scan(&total)
db.Model(&Order{}).Select("AVG(amount)").Scan(&avg)
db.Model(&Order{}).Select("MAX(amount)").Scan(&max)

// 分组
db.Model(&Order{}).Select("user_id, SUM(amount)").Group("user_id").Find(&results)

// Having
db.Model(&Order{}).Select("user_id, COUNT(*) as cnt").
    Group("user_id").Having("COUNT(*) > ?", 5).Find(&results)

// 原生 SQL
type Result struct {
    ID   uint
    Name string
}
db.Raw("SELECT id, name FROM users WHERE age > ?", 20).Scan(&results)
db.Exec("UPDATE users SET age = ? WHERE id = ?", 30, 1)

更新 ​

go
var user User
db.First(&user, 1)

// 更新单个字段
db.Model(&user).Update("Name", "New Name")

// 更新多个字段
db.Model(&user).Updates(map[string]interface{}{
    "Name":  "New Name",
    "Email": "new@example.com",
})

// 使用结构体更新
db.Model(&user).Updates(User{Name: "New Name"})

// 选择字段更新
db.Model(&user).Select("Name", "Age").Updates(User{
    Name:  "New",
    Age:   30,
    Email: "unchanged@example.com",  // 被忽略
})

// 忽略字段更新
db.Model(&user).Omit("Email").Updates(user)

// 更新所有字段 (包括零值)
db.Model(&user).Select(clause.Associations).Updates(&user)

// 条件更新
db.Model(&User{}).Where("age < ?", 18).Update("status", "minor")

// 批量更新
db.Model(&User{}).Where("id IN ?", []int{1, 2, 3}).Update("status", "active")

删除 ​

go
var user User
db.First(&user, 1)

// 软删除 (需要 DeletedAt 字段)
db.Delete(&user)
db.Where("id = ?", 1).Delete(&User{})

// 硬删除 (需要 Remove Soft Delete)
db.Unscoped().Delete(&user)
db.Unscoped().Where("id = ?", 1).Delete(&User{})

// 批量删除
db.Where("age < ?", 18).Delete(&User{})

// 根据主键删除
db.Delete(&User{}, 1)
db.Delete(&User{}, []int{1, 2, 3})

关联查询 ​

一对一 ​

go
// 定义
type User struct {
    ID      uint
    Name    string
    Profile *Profile  // has one
}

type Profile struct {
    ID     uint
    UserID uint
    Bio    string
}

// 查询
var user User
db.Preload("Profile").First(&user, 1)

// 条件预加载
db.Preload("Profile", "bio LIKE ?", "%developer%").First(&user)

// 嵌套预加载
db.Preload("Profile").Preload("Profile.Address").First(&user)

// 所有关联
db.Preload(clause.Associations).Find(&users)

一对多 ​

go
// 定义
type User struct {
    ID     uint
    Name   string
    Orders []Order  // has many
}

type Order struct {
    ID     uint
    UserID uint
    User   User   `gorm:"foreignKey:UserID"`
    Amount int64
}

// 查询
var user User
db.Preload("Orders").First(&user, 1)

// 条件预加载
db.Preload("Orders", "amount > ?", 10000).First(&user)

// 限制预加载数量
db.Preload(clause.Associations).Find(&users)

多对多 ​

go
// 定义
type User struct {
    ID    uint
    Name  string
    Roles []Role `gorm:"many2many:user_roles;"`
}

type Role struct {
    ID    uint
    Name  string
    Users []User `gorm:"many2many:user_roles;"`
}

// 查询
var user User
db.Preload("Roles").First(&user, 1)

// 条件预加载
db.Preload("Roles", "name IN ?", []string{"admin", "user"}).First(&user)

// 关联查询
var roles []Role
db.Model(&user).Association("Roles").Find(&roles)

// 关联添加
db.Model(&user).Association("Roles").Append(&Role{ID: 1})

// 关联替换
db.Model(&user).Association("Roles").Replace([]Role{role1, role2})

// 关联删除
db.Model(&user).Association("Roles").Delete(&role1)

// 关联清空
db.Model(&user).Association("Roles").Clear()

// 关联计数
db.Model(&user).Association("Roles").Count()

事务 ​

go
// 基础事务
tx := db.Begin()
defer func() {
    if r := recover(); r != nil {
        tx.Rollback()
    }
}()

var user User
if err := tx.First(&user, 1).Error; err != nil {
    tx.Rollback()
    return
}

if err := tx.Model(&user).Update("age", 30).Error; err != nil {
    tx.Rollback()
    return
}

if err := tx.Create(&Order{UserID: user.ID, Amount: 100}).Error; err != nil {
    tx.Rollback()
    return
}

// 提交事务
if err := tx.Commit().Error; err != nil {
    return
}

// 事务闭包
err := db.Transaction(func(tx *gorm.DB) error {
    var user User
    if err := tx.First(&user, 1).Error; err != nil {
        return err
    }

    if err := tx.Model(&user).Update("age", 30).Error; err != nil {
        return err
    }

    return tx.Create(&Order{UserID: user.ID}).Error
})

// 带保存点的事务
db.Transaction(func(tx1 *gorm.DB) error {
    tx1.Create(&User{Name: "Alice"})

    // 保存点
    tx1.SavePoint("before_order")

    if err := tx1.Create(&Order{}).Error; err != nil {
        // 回滚到保存点
        tx1.RollbackTo("before_order")
    }

    return nil
})

钩子 (Hooks) ​

go
type User struct {
    ID   uint
    Name string
}

// 创建前
func (u *User) BeforeCreate(tx *gorm.DB) error {
    // 加密密码
    hash, _ := bcrypt.GenerateFromPassword([]byte(u.Password), 10)
    u.Password = string(hash)

    // 设置默认值
    if u.Status == "" {
        u.Status = "active"
    }

    return nil
}

// 创建后
func (u *User) AfterCreate(tx *gorm.DB) error {
    // 发送欢迎邮件
    go sendWelcomeEmail(u.Email)
    return nil
}

// 更新前
func (u *User) BeforeUpdate(tx *gorm.DB) error {
    u.UpdatedAt = time.Now()
    return nil
}

// 删除前
func (u *User) BeforeDelete(tx *gorm.DB) error {
    // 检查是否可以删除
    var count int64
    tx.Model(&Order{}).Where("user_id = ?", u.ID).Count(&count)
    if count > 0 {
        return errors.New("用户有未完成的订单")
    }
    return nil
}

// 查找后
func (u *User) AfterFind(tx *gorm.DB) error {
    // 计算字段
    u.FullName = u.FirstName + " " + u.LastName
    return nil
}

高级查询 ​

子查询 ​

go
// WHERE in 子查询
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&users)

// 条件子查询
db.Where("id IN (?)", db.Table("orders").Select("user_id").Where("amount > ?", 100)).Find(&users)

// 带别名的子查询
db.Select("u.*").Table("users as u").
    Where("u.id IN (?)", db.Table("orders").Select("user_id")).Find(&results)

Join 查询 ​

go
type Result struct {
    UserName  string
    OrderNo   string
    Amount    int64
}

db.Table("users").
    Select("users.name as user_name, orders.order_no, orders.amount").
    Joins("LEFT JOIN orders ON orders.user_id = users.id").
    Where("orders.amount > ?", 100).
    Scan(&results)

// 使用条件 Join
db.Joins("JOIN orders ON orders.user_id = users.id AND orders.status = ?", 1).Find(&users)

// Join 预加载
db.Joins("Profile").Find(&users)

自定义查询 ​

go
// Scopes 定义可复用的查询条件
func AmountGreaterThan100(db *gorm.DB) *gorm.DB {
    return db.Where("amount > ?", 100)
}

func StatusActive(db *gorm.DB) *gorm.DB {
    return db.Where("status = ?", "active")
}

// 使用
db.Scopes(AmountGreaterThan100, StatusActive).Find(&orders)

// 带参数的 Scope
func WithName(name string) func(db *gorm.DB) *gorm.DB {
    return func(db *gorm.DB) *gorm.DB {
        return db.Where("name LIKE ?", "%"+name+"%")
    }
}

db.Scopes(WithName("alice")).Find(&users)

性能优化 ​

go
// N+1 问题 - 使用 Preload
// 不好
var users []User
db.Find(&users)
for i := range users {
    db.Find(&users[i].Orders)  // N 次查询
}

// 好
db.Preload("Orders").Find(&users)  // 2 次查询

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

// 批量操作
users := []User{{Name: "A"}, {Name: "B"}, {Name: "C"}}
db.Create(&users)  // 一次插入

// 使用 FindInBatches 处理大数据
var users []User
db.FindInBatches(&users, 100, func(tx *gorm.DB, batch int) error {
    for _, u := range users {
        // 处理
    }
    return nil
})

// 禁用钩子提升性能
db.Session(&gorm.Session{SkipHooks: true}).Create(&user)

// 准备语句
db = db.Session(&gorm.Session{PrepareStmt: true})

// 缓存 (使用插件或自定义)

GORM 检查清单 ​

[ ] 使用指针接收者定义钩子
[ ] 为软删除添加 DeletedAt 字段
[ ] 使用 Preload 避免 N+1
[ ] 事务使用闭包方式
[ ] 批量操作减少数据库交互
[ ] 为常用查询添加索引
[ ] 使用 Select 只查询需要的字段
[ ] 使用 Scopes 复用查询条件
[ ] 设置合理的连接池大小
[ ] 使用 FindInBatches 处理大数据
[ ] 错误处理检查 gorm.ErrRecordNotFound