GORM 把 SQL 查询结果映射到 Go 结构体,但它没有消除 SQL、事务和连接池。理解 &user、链式查询返回值和关联规则,才能避免“能查到数据但不知道为什么”。

基础模型

1
2
3
4
5
6
7
8
9
10
11
12
13
type User struct {
ID uint
Name string
Email string
Orders []Order
}

type Order struct {
ID uint
UserID uint
Amount int64
Status string
}

GORM 按约定用 Order.UserID 关联 User.ID。约定不符合业务模型时,应显式配置外键,不要依赖猜测。

&user 是什么

1
2
var user User
tx := db.First(&user, 10)

&user 是可写地址,GORM 用它接收查询结果;查询条件来自 10,表示按主键查。tx 是本次数据库操作对象,包含 Error 和 RowsAffected 等信息。

1
2
3
4
5
6
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
return nil
}
if tx.Error != nil {
return tx.Error
}

查询单条记录为空会返回 ErrRecordNotFound;批量 Find 通常返回空切片而不是这个错误。

多个条件

1
2
3
4
5
6
7
var users []User
tx := db.
Where("status = ?", "active").
Where("created_at >= ?", startTime).
Order("id DESC").
Limit(20).
Find(&users)

值应通过参数绑定传入。动态排序字段必须来自白名单,不能直接拼接用户输入。

创建与字段回填

1
2
3
4
5
6
7
user := User{Name: "Leroi", Email: "leroi@example.com"}

if err := db.Create(&user).Error; err != nil {
return err
}

fmt.Println(user.ID)

主键和时间戳可能由数据库或 GORM 在写入后回填到原结构体,因此需要传指针。

Preload 与 N+1

1
2
3
4
var users []User
err := db.
Preload("Orders", "status = ?", "paid").
Find(&users).Error

Preload 通常用额外批量查询加载关联,避免循环逐个查订单。它不是自动 Join,仍要查看生成 SQL、结果规模和索引。

事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&order).Error; err != nil {
return err
}

result := tx.Model(&Product{}).
Where("id = ? AND stock > 0", productID).
Update("stock", gorm.Expr("stock - 1"))

if result.Error != nil {
return result.Error
}
if result.RowsAffected != 1 {
return errors.New("out of stock")
}
return nil
})

回调返回错误会回滚。事务里不要调用慢 HTTP 接口,否则会延长持锁和占用连接的时间。

连接池

1
2
3
4
5
6
7
8
sqlDB, err := db.DB()
if err != nil {
return err
}

sqlDB.SetMaxOpenConns(40)
sqlDB.SetMaxIdleConns(10)
sqlDB.SetConnMaxLifetime(30 * time.Minute)

每个 Goroutine 发起一次查询,就会尝试从连接池获得连接。Goroutine 很轻量不代表数据库连接无限,池大小要和实例数、数据库上限及查询耗时共同计算。

延伸阅读

站内搜索

没有找到内容!