6.4. GORM ORM 框架

6.4.1. GORM 概述

GORM 是 Go 语言中最流行的 ORM 框架,功能全面且易于使用。

6.4.2. 安装

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

6.4.3. 连接数据库

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

func main() {
    dsn := "user:password@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 {
        panic("failed to connect database")
    }
    
    // 配置连接池
    sqlDB, _ := db.DB()
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)
    sqlDB.SetConnMaxLifetime(time.Hour)
}

6.4.4. 模型定义

type User struct {
    ID        uint           `gorm:"primaryKey"`
    Name      string         `gorm:"size:255;not null"`
    Email     string         `gorm:"uniqueIndex;size:255"`
    Age       int            `gorm:"default:18"`
    Active    bool           `gorm:"default:true"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"` // 软删除
}

// 自定义表名
func (User) TableName() string {
    return "users"
}

6.4.5. CRUD 操作

6.4.5.1. 创建

// 创建记录
user := User{Name: "Alice", Email: "alice@example.com"}
result := db.Create(&user)
// user.ID 会被自动赋值

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

// 选择性创建
db.Select("Name", "Email").Create(&user)

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

6.4.5.2. 查询

// 查询单条记录
var user User
db.First(&user, 1)                    // 主键查询
db.First(&user, "name = ?", "Alice")  // 条件查询

// 查询所有
var users []User
db.Find(&users)

// 条件查询
db.Where("age > ?", 18).Find(&users)
db.Where(&User{Name: "Alice"}).Find(&users)
db.Where(map[string]interface{}{"name": "Alice"}).Find(&users)

// 链式条件
db.Where("name = ?", "Alice").
    Where("age > ?", 18).
    Find(&users)

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

// 排序和分页
db.Order("created_at desc").
    Limit(10).
    Offset(0).
    Find(&users)

6.4.5.3. 更新

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

// 更新多个字段
db.Model(&user).Updates(User{Name: "Bob", Age: 20})
db.Model(&user).Updates(map[string]interface{}{"name": "Bob", "age": 20})

// 批量更新
db.Model(&User{}).Where("age < ?", 18).Update("active", false)

// 使用 Select 指定更新字段
db.Model(&user).Select("Name").Updates(User{Name: "Bob", Age: 0})

// 使用 Omit 排除字段
db.Model(&user).Omit("Age").Updates(User{Name: "Bob", Age: 0})

6.4.5.4. 删除

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

// 永久删除
db.Unscoped().Delete(&user)

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

// 批量删除
db.Delete(&User{}, []int{1, 2, 3})

6.4.6. 关联

6.4.6.1. 一对一

type User struct {
    ID      uint
    Name    string
    Profile Profile  // has one
}

type Profile struct {
    ID     uint
    UserID uint
    Bio    string
}

// 查询包含关联
db.Preload("Profile").Find(&users)

6.4.6.2. 一对多

type User struct {
    ID    uint
    Name  string
    Posts []Post  // has many
}

type Post struct {
    ID     uint
    UserID uint
    Title  string
}

// 预加载
db.Preload("Posts").Find(&users)

// 条件预加载
db.Preload("Posts", "published = ?", true).Find(&users)

6.4.6.3. 多对多

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;"`
}

// 预加载
db.Preload("Roles").Find(&users)

6.4.7. ⚠️ 常见陷阱

6.4.7.1. 陷阱 1:零值更新问题

// ❌ 零值不会更新
db.Model(&user).Updates(User{Name: "Bob", Age: 0})
// Age 不会被更新为 0

// ✅ 使用 map 或 Select
db.Model(&user).Updates(map[string]interface{}{"age": 0})
db.Model(&user).Select("Age").Updates(User{Age: 0})

6.4.7.2. 陷阱 2:N+1 查询

// ❌ N+1 问题
var users []User
db.Find(&users)
for _, user := range users {
    var posts []Post
    db.Where("user_id = ?", user.ID).Find(&posts)  // 每个用户都查一次
}

// ✅ 使用 Preload
db.Preload("Posts").Find(&users)

6.4.7.3. 陷阱 3:goroutine 中共享 DB

// ❌ DB 实例是安全的,但 Session 不是
db.Model(&user).Update("name", "Alice")  // 在多个 goroutine 中这样用是安全的

// ❌ 但是 Session 不安全
tx := db.Begin()
go func() {
    tx.Create(&user1)  // 不安全!
}()
go func() {
    tx.Create(&user2)  // 不安全!
}()

// ✅ 每个 goroutine 使用独立的 Session
go func() {
    tx := db.Session(&gorm.Session{})
    tx.Create(&user1)
}()

6.4.7.4. 陷阱 4:忽略错误

// ❌ 忽略错误
db.Create(&user)

// ✅ 检查错误
if err := db.Create(&user).Error; err != nil {
    return err
}

// 或者
result := db.Create(&user)
if result.Error != nil {
    return result.Error
}
fmt.Println("Rows affected:", result.RowsAffected)

6.4.8. 事务

// 自动事务
err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&user).Error; err != nil {
        return err  // 返回错误会自动回滚
    }
    if err := tx.Create(&post).Error; err != nil {
        return err
    }
    return nil  // 返回 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(&post).Error; err != nil {
    tx.Rollback()
    return err
}

tx.Commit()

6.4.9. 钩子

type User struct {
    ID   uint
    Name string
    Hash string
}

func (u *User) BeforeCreate(tx *gorm.DB) error {
    u.Hash = generateHash(u.Name)
    return nil
}

func (u *User) AfterCreate(tx *gorm.DB) error {
    // 创建后的操作
    return nil
}

// 可用钩子:
// BeforeSave, AfterSave
// BeforeCreate, AfterCreate
// BeforeUpdate, AfterUpdate
// BeforeDelete, AfterDelete
// AfterFind

6.4.10. 原生 SQL

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

// 原生执行
db.Exec("UPDATE users SET age = ? WHERE name = ?", 20, "Alice")

6.4.11. 参考资源