GORM与Fiber需手动桥接:在fiber.New()后初始化gorm.DB并配置连接池,推荐封装为App结构体(含fiber.App和*gorm.DB),通过WithContext(c.Context())传递请求上下文以支持取消信号,避免goroutine泄漏。

GORM 与 Fiber 能无缝配合,但默认不共享上下文、连接池或错误处理逻辑,必须手动桥接
怎么在 Fiber 启动时初始化 GORM 并注入 App
Fiber 本身不管理数据库连接,你需要在 app := fiber.New() 之前或紧随其后完成 GORM 初始化,并把 *gorm.DB 存到 app.Settings 或全局变量中。推荐用 app.Use 中间件挂载,避免全局污染:
- 先调用
gorm.Open()获取*gorm.DB,务必配置SetMaxOpenConns和SetMaxIdleConns,否则高并发下容易耗尽连接 - 不要把
db直接塞进fiber.Ctx.Locals—— 它只存请求级数据,不适合放 DB 实例 - 更稳妥的做法是定义一个结构体(如
App)封装*fiber.App和*gorm.DB,所有 handler 接收该结构体指针
示例片段:
type App struct {
fiber *fiber.App
db *gorm.DB
}
func NewApp() *App {
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
db.DB().SetMaxOpenConns(50)
db.DB().SetMaxIdleConns(10)
app := fiber.New()
return &App{fiber: app, db: db}
}
为什么用 db.WithContext(c.Context()) 而不是直接传 c
Fiber 的 fiber.Ctx 不是标准 context.Context,但 GORM v2 的所有方法(如 Create、First)都接受 WithContext() 链式调用。直接传 c 会编译报错:cannot use c (type *fiber.Ctx) as type context.Context。
- 正确写法是
app.db.WithContext(c.Context()).First(&user, id) - 这能让 GORM 继承请求上下文的取消信号(比如客户端断连时自动中断查询),避免 goroutine 泄漏
- 若忽略此步,超时、取消等控制全部失效,尤其在长查询或关联加载场景下风险明显
Find / First 返回空结果却不报错,怎么判断记录是否存在
GORM 的 First、Take、Last 在查不到数据时返回 gorm.ErrRecordNotFound,但 Find 默认不报错——它只填充切片,查不到就是空切片。这点和很多 ORM 不同,容易误判。
- 用
First查单条且需明确区分「不存在」和「其他错误」时,必须显式检查:if errors.Is(err, gorm.ErrRecordNotFound) - 用
Find查列表时,别依赖err != nil判断有无数据,应检查len(users) == 0 - 软删除(
DeletedAt)启用后,First默认跳过已软删记录;如需包含,得加Unscoped(),否则逻辑可能出偏
更新操作用 Save 还是 Updates?字段零值怎么处理
Save 是全量更新(会覆盖所有字段,包括传入的零值),Updates 是部分更新(只改传入字段,忽略零值)。但 GORM 对零值的判定有陷阱:
-
Updates(map[string]interface{})中,"age": 0会被写入;而Updates(&user)中,user.Age = 0默认被忽略(除非显式用Select("Age")) - 想安全更新零值字段,要么用
map[string]interface{}形式,要么用db.Select("Age", "Name").Updates(&user) -
Save会触发钩子(BeforeSave等),Updates不触发 —— 如果业务依赖钩子做审计或时间戳更新,这点必须对齐
真正难缠的是时间字段:GORM 默认把 time.Time{} 当零值跳过,但 JSON 解析后常得到非零但无效的时间(如 "0001-01-01T00:00:00Z"),它既不是零值也不合法。这种情况下必须预处理或自定义类型(如 HTime)。


















