Files
cs-note/hzh/GolangStar/Go语言框架/gorm.md
T

5.4 KiB
Raw Blame History

tags, create time
tags create time
go
golang
gorm
ORM
数据库
2026-06-07 15:25

GORM

概述

GORM 是 Go 生态中最流行的 ORM 库,提供简洁的链式 API 操作数据库。本文从连接配置到 CRUD、关联关系和事务,覆盖日常开发的核心用法。

正文

安装

go get -gorm.io/gorm
go get -gorm.io/driver/mysql   # MySQL 驱动(按需选择)

连接数据库

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{})
if err != nil {
    log.Fatal("failed to connect database:", err)
}

// 可选:调优连接池
sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(100)   // 最大连接数
sqlDB.SetMaxIdleConns(10)    // 最大空闲连接

[!tip] 💡 性能建议

  • SetMaxOpenConns 应根据并发量和请求耗时调整——IO 密集型可适当放大
  • SetMaxIdleConns 一般设为 MaxOpenConns 的 1/4~1/2
  • 连接泄漏(未正确关闭)会导致连接池耗尽,务必注意

模型定义

type User struct {
    ID       uint   `gorm:"primaryKey"`
    Name     string `gorm:"size:255;not null"`
    Age      int
    Email    string `gorm:"uniqueIndex"`
    CreatedAt time.Time
}

// 表名默认为复数形式 "users",可通过 TableName 自定义
func (User) TableName() string {
    return "app_users"
}

[!info] ℹ️ GORM 命名约定

  • 表名:结构体名的蛇形复数(User → users)
  • 字段名:驼峰转蛇形(UserID → user_id)
  • 主键:自动使用 ID / uuid / uid 作为主键并自增
  • 外键:关联字段名 + _id

CRUD 操作

创建

user := User{Name: "张三", Age: 25}
result := db.Create(&user) // 自动回填 user.ID
fmt.Println(user.ID)       // 查看插入后的 ID

查询

// 根据主键查询
var u1 User
db.First(&u1, 1)           // SELECT * FROM users WHERE id = 1

// 条件查询
var u2 []User
db.Where("age > ?", 18).Find(&u2)

// 链式组合
db.Where("name LIKE ?", "%张%").Order("age desc").Limit(10).Find(&u2)

[!question] 💭 ORM vs 原生 SQL GORM 的链式调用确实优雅,但在复杂查询场景下是否值得牺牲 SQL 的表达能力?思考你的项目中哪些查询可以用 GORM 表达,哪些需要手写 SQL。

更新

// 更新单个字段
db.Model(&user).Update("name", "李四")

// 更新多个字段
db.Model(&user).Updates(User{Name: "王五", Age: 30})

// 全量更新(注意:会忽略零值字段!)
db.Save(&user)

[!warning] ⚠️ Save 的零值陷阱 Save() 会用结构体的所有字段执行 UPDATE。如果某个字段是零值(如 Age: 0),会被更新为 0!建议使用 Updates 并只传需要更新的字段。

删除

db.Delete(&user, 1)                    // 软删除(需启用)或硬删除
db.Where("age < ?", 18).Delete(&User{}) // 批量删除

[!note] 📝 软删除 GORM 默认启用软删除——调用 Delete 后记录不会被物理删除,而是设置 deleted_at 时间戳。如需物理删除:db.Unscoped().Delete(&user)

关联关系

type Profile struct {
    ID   uint
    UserID uint `gorm:"index"`
    Bio  string
}

type Post struct {
    ID      uint
    AuthorID uint `gorm:"index"`
    Title   string
    Author  User   `gorm:"foreignKey:AuthorID"` // 指定外键
}

// 预加载关联数据
db.Preload("Profile").First(&user, 1)
// SELECT * FROM profiles WHERE user_id = 1

[!tip] 💡 N+1 问题 在循环中逐个查询关联数据会产生 N+1 问题。始终使用 Preload 一次性加载:

// ❌ N+1
for _, post := range posts {
    db.First(&post.Author)
}

// ✅ 一次加载
db.Preload("Author").Find(&posts)

事务

err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&User{Name: "Alice"}).Error; err != nil {
        return err // 返回非零错误触发回滚
    }
    if err := tx.Create(&User{Name: "Bob"}).Error; err != nil {
        return err
    }
    return nil // 返回 nil 则提交
})

[!warning] ⚠️ 事务中的常见错误

  • 不要在事务中使用全局 db 变量,应使用传入的 tx
  • 事务内的所有操作都必须检查 .Error
  • 事务必须在同一个连接上执行(GORM 默认保证这一点)

ORM vs NORM

[!question] 💭 思考 GORM 属于 ORM(对象关系映射),而 Gin 搭配 sqlx 属于 NORM(无映射)。什么时候该用 ORM,什么时候该用 NORM?

维度 ORM (GORM) NORM (sqlx/raw SQL)
开发效率 高(链式 API) 低(需写 SQL)
类型安全 编译期部分保障 完全依赖 SQL 编写
复杂查询 有限支持 完全自由
性能 有额外开销 最优
学习曲线 低 需熟悉 SQL

[!tip] 💡 混合策略 大多数项目可以采用混合模式:简单 CRUD 用 GORM,复杂报表/分析查询用原生 SQL:

// 简单查询用 GORM
db.Find(&users)

// 复杂查询用原生 SQL
var results []struct {
    Department string
    AvgAge     float64
}
db.Raw(`SELECT department, AVG(age) as avg_age FROM users GROUP BY department`).Scan(&results)

关联笔记