Skip to content
清晨的一缕阳光
返回

GORM 实战指南 - Go 语言 ORM 最佳实践

GORM(Go ORM)是 Go 语言最流行的对象关系映射库,提供模型定义、CRUD 操作、关联查询、事务处理等完整功能。本文将深入探讨 GORM 的实战应用和最佳实践。

一、GORM 简介

1.1 核心特性

特性说明
全功能 ORM模型定义、CRUD、关联、事务
自动迁移根据模型自动创建/更新表结构
钩子函数Before/After Save/Update/Delete
预加载优雅解决 N+1 查询问题
多数据库MySQL、PostgreSQL、SQLite、SQLServer

1.2 安装与配置

go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

连接数据库

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{
        Logger: logger.Default.LogMode(logger.Info),
    })
    if err != nil {
        panic("failed to connect database")
    }
    
    // 连接池配置
    sqlDB, _ := db.DB()
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)
    sqlDB.SetConnMaxLifetime(time.Hour)
}

二、模型定义

2.1 基础模型

package model

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

// User 用户模型
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:"-"`
    
    // 字段定义
    Username string  `gorm:"size:50;uniqueIndex;not null" json:"username"`
    Password string  `gorm:"size:100;not null" json:"-"`
    Email    string  `gorm:"size:100;index" json:"email"`
    Age      int     `gorm:"comment:年龄" json:"age"`
    Status   int     `gorm:"default:1" json:"status"`
    
    // 关联
    Profile  *Profile `json:"profile"`
    Orders   []Order  `json:"orders"`
    Roles    []Role   `gorm:"many2many:user_roles;" json:"roles"`
}

// Profile 用户档案
type Profile struct {
    ID        uint `gorm:"primaryKey"`
    UserID    uint `gorm:"uniqueIndex;not null"`
    Avatar    string `gorm:"size:255"`
    Bio       string `gorm:"type:text"`
    Website   string `gorm:"size:255"`
}

// Order 订单
type Order struct {
    ID        uint `gorm:"primaryKey"`
    OrderNo   string `gorm:"size:50;uniqueIndex;not null"`
    UserID    uint `gorm:"index;not null"`
    Amount    float64 `gorm:"type:decimal(10,2);not null"`
    Status    int `gorm:"default:0"`
    User      *User `json:"user,omitempty"`
    Items     []OrderItem `json:"items"`
}

// OrderItem 订单项
type OrderItem struct {
    ID        uint `gorm:"primaryKey"`
    OrderID   uint `gorm:"index;not null"`
    ProductID uint `gorm:"index;not null"`
    Quantity  int `gorm:"not null"`
    Price     float64 `gorm:"type:decimal(10,2);not null"`
}

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

2.2 高级模型特性

type Product struct {
    ID         uint   `gorm:"primaryKey"`
    Code       string `gorm:"uniqueIndex;size:20"`
    Price      float64 `gorm:"type:decimal(10,2)"`
    
    // 忽略字段
    Ignored    string `gorm:"-"`
    
    // 默认值
    Status     int    `gorm:"default:1"`
    
    // 自动创建时间
    CreatedAt  time.Time
    
    // 指定表名
    gorm.Model
}

// 自定义表名
func (Product) TableName() string {
    return "products"
}

三、CRUD 操作

3.1 创建数据

// 单条创建
user := User{Username: "张三", Email: "zhangsan@example.com", Age: 25}
db.Create(&user)

// 批量创建
users := []User{
    {Username: "李四", Email: "lisi@example.com"},
    {Username: "王五", Email: "wangwu@example.com"},
}
db.Create(&users)

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

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

3.2 查询数据

var user User
var users []User

// 根据主键查询
db.First(&user, 1)
db.Take(&user, 1)

// 条件查询
db.Where("username = ?", "张三").First(&user)
db.Where("age > ? AND status = ?", 18, 1).Find(&users)

// Map 条件
db.Where(map[string]interface{}{"username": "张三", "status": 1}).Find(&users)

// IN 查询
db.Where("username IN ?", []string{"张三", "李四"}).Find(&users)

// 模糊查询
db.Where("username LIKE ?", "%张%").Find(&users)

// 获取所有
db.Find(&users)

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

// 排序
db.Order("age desc, username asc").Find(&users)

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

3.3 更新数据

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

// 更新多个字段
db.Model(&user).Updates(map[string]interface{}{
    "Age":    26,
    "Status": 1,
})

// 使用结构体更新(只更新非零值)
db.Model(&user).Updates(User{Age: 26, Status: 1})

// 更新所有字段(包括零值)
db.Model(&user).Select("*").Updates(map[string]interface{}{
    "Age":    0,
    "Status": 0,
})

3.4 删除数据

// 软删除(需要模型有 DeletedAt 字段)
db.Delete(&user)

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

// 条件删除
db.Where("status = ?", 0).Delete(&User{})

// 批量删除
db.Delete(&users)

四、关联查询

4.1 一对一

// 创建用户和档案
user := User{
    Username: "张三",
    Profile: &Profile{
        Avatar:  "avatar.jpg",
        Bio:     "简介",
        Website: "https://example.com",
    },
}
db.Create(&user)

// 预加载档案
var user User
db.Preload("Profile").First(&user, 1)

4.2 一对多

// 创建用户和订单
user := User{
    Username: "张三",
    Orders: []Order{
        {OrderNo: "ORD001", Amount: 100},
        {OrderNo: "ORD002", Amount: 200},
    },
}
db.Create(&user)

// 预加载订单
var user User
db.Preload("Orders").First(&user, 1)

// 条件预加载
db.Preload("Orders", "status = ?", 1).First(&user, 1)

// 嵌套预加载
db.Preload("Orders.Items").First(&user, 1)

4.3 多对多

// 创建用户和角色
user := User{
    Username: "张三",
    Roles: []Role{
        {Name: "admin"},
        {Name: "user"},
    },
}
db.Create(&user)

// 预加载角色
var user User
db.Preload("Roles").First(&user, 1)

// 关联查询
var roles []Role
db.Model(&user).Where("name = ?", "admin").Association("Roles").Find(&roles)

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

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

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

4.4 解决 N+1 问题

// ❌ N+1 查询
var users []User
db.Find(&users)
for i := range users {
    var profile Profile
    db.Where("user_id = ?", users[i].ID).First(&profile)
}

// ✅ 使用预加载
var users []User
db.Preload("Profile").Find(&users)

五、事务处理

5.1 基础事务

func TransferMoney(db *gorm.DB, fromID, toID uint, amount float64) error {
    // 开启事务
    tx := db.Begin()
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback()
        }
    }()
    
    // 扣款
    if err := tx.Model(&User{}).Where("id = ?", fromID).
        Update("balance", gorm.Expr("balance - ?", amount)).Error; err != nil {
        tx.Rollback()
        return err
    }
    
    // 收款
    if err := tx.Model(&User{}).Where("id = ?", toID).
        Update("balance", gorm.Expr("balance + ?", amount)).Error; err != nil {
        tx.Rollback()
        return err
    }
    
    // 提交事务
    return tx.Commit().Error
}

5.2 事务选项

// 使用 WithContext
tx := db.WithContext(ctx).Begin()

// 设置隔离级别
tx = db.Session(&gorm.Session{
    PrepareStmt: true,
}).Begin()

// 保存点
tx.SavePoint("save1")
tx.RollbackTo("save1")

六、钩子函数

type User struct {
    ID       uint
    Username string
    Password string
    Email    string
}

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

// 更新前钩子
func (u *User) BeforeUpdate(tx *gorm.DB) error {
    // 验证邮箱
    if !isValidEmail(u.Email) {
        return errors.New("invalid email")
    }
    return nil
}

// 删除后钩子
func (u *User) AfterDelete(tx *gorm.DB) error {
    // 清理关联
    tx.Where("user_id = ?", u.ID).Delete(&Profile{})
    return nil
}

七、性能优化

7.1 索引优化

type Product struct {
    ID       uint   `gorm:"primaryKey"`
    Code     string `gorm:"uniqueIndex;size:20"`
    Category string `gorm:"index"`
    Price    float64 `gorm:"type:decimal(10,2);index"`
}

7.2 批量操作

// ❌ 低效
for _, user := range users {
    db.Create(&user)
}

// ✅ 高效
db.Create(&users)

7.3 选择字段

// 只查询需要的字段
db.Select("id", "username", "email").Find(&users)

// 排除大字段
db.Omit("bio", "avatar").Find(&users)

7.4 禁用钩子

// 批量导入时禁用钩子提升性能
db.Session(&gorm.Session{SkipHooks: true}).Create(&users)

八、实战案例

8.1 分页查询

func Paginate[T any](db *gorm.DB, page, pageSize int) *gorm.DB {
    if page < 1 {
        page = 1
    }
    if pageSize < 1 {
        pageSize = 10
    }
    offset := (page - 1) * pageSize
    return db.Offset(offset).Limit(pageSize)
}

// 使用
var users []User
var total int64
db.Model(&User{}).Count(&total)
Paginate[User](db, 1, 10).Find(&users)

8.2 软删除查询

// 查询未删除的数据
db.Where("deleted_at IS NULL").Find(&users)

// 查询包括已删除的数据
db.Unscoped().Find(&users)

// 恢复已删除的数据
db.Unscoped().Where("id = ?", 1).Update("deleted_at", nil)

8.3 动态查询

type UserQuery struct {
    Username string `form:"username"`
    Email    string `form:"email"`
    MinAge   int    `form:"min_age"`
    MaxAge   int    `form:"max_age"`
}

func SearchUsers(db *gorm.DB, query UserQuery) ([]User, error) {
    var users []User
    tx := db.Model(&User{})
    
    if query.Username != "" {
        tx = tx.Where("username LIKE ?", "%"+query.Username+"%")
    }
    if query.Email != "" {
        tx = tx.Where("email LIKE ?", "%"+query.Email+"%")
    }
    if query.MinAge > 0 {
        tx = tx.Where("age >= ?", query.MinAge)
    }
    if query.MaxAge > 0 {
        tx = tx.Where("age <= ?", query.MaxAge)
    }
    
    err := tx.Find(&users).Error
    return users, err
}

九、常见问题

9.1 字段为零值不更新

// ❌ 问题:Age=0 不会更新
db.Model(&user).Updates(User{Age: 0, Status: 1})

// ✅ 解决方案 1:使用 Map
db.Model(&user).Updates(map[string]interface{}{"Age": 0, "Status": 1})

// ✅ 解决方案 2:Select 所有字段
db.Model(&user).Select("*").Updates(User{Age: 0, Status: 1})

9.2 关联不自动保存

// ❌ 问题:关联数据不会自动保存
user := User{Username: "张三", Profile: &Profile{Bio: "简介"}}
db.Create(&user) // Profile 不会保存

// ✅ 解决方案:显式创建
db.Create(&user.Profile)

十、总结

GORM 最佳实践

实践说明
使用预加载避免 N+1 查询
批量操作提升写入性能
事务处理保证数据一致性
索引优化提升查询性能
选择字段减少数据传输

参考资料


分享这篇文章到:

上一篇文章
消息队列选型指南:Kafka vs RocketMQ vs RabbitMQ
下一篇文章
Redis 计数器实战