什么是 GORM,它的核心特性有哪些?
GORM 是什么
GORM 是 Go 语言中应用最广泛的 ORM 库,基于反射机制将结构体映射为数据库表,将方法调用转换为 SQL 语句。它遵循约定优于配置的原则——结构体名的蛇形复数即为表名,ID 字段默认为主键,CreatedAt / UpdatedAt 自动管理时间戳。
核心特性
- 关联关系:支持 Has One、Has Many、Belongs To、Many To Many 四种关联,通过
Preload预加载或Joins联表查询获取关联数据 - 钩子机制:在 Create / Update / Delete / Find 前后可注册回调,例如
BeforeCreate中做字段默认值填充,AfterDelete中清理关联资源 - 事务支持:
db.Transaction(func(tx *gorm.DB) error { ... })提供闭包式事务,返回 error 自动回滚,返回 nil 自动提交 - 自动迁移:
db.AutoMigrate(&User{})根据结构体定义同步表结构(新增列、索引),但不会删除已有列 - 链式调用:
db.Where(...).Order(...).Limit(...).Find(&results)风格的查询构建器,中间态可复用
基本 CRUD 示例
gotype User struct { gorm.Model // 内置 ID、CreatedAt、UpdatedAt、DeletedAt Name string Email string `gorm:"type:varchar(100);uniqueIndex"` Age int } // 创建 db.Create(&User{Name: "Alice", Email: "alice@test.com", Age: 28}) // 查询 var user User db.First(&user, 1) // 按主键 db.Where("age > ?", 20).Find(&users) // 条件查询 // 更新 db.Model(&user).Update("age", 29) // 单字段 db.Model(&user).Updates(map[string]interface{}{"age": 29, "name": "Alice W"}) // 多字段 // 删除(软删除,DeletedAt 非空) db.Delete(&user) db.Unscoped().Delete(&user) // 硬删除
面试高频追问
Q1:GORM 的软删除是如何实现的?如何查询被软删除的记录?
GORM 在模型中嵌入 gorm.Model 后会包含 DeletedAt 字段(类型为 gorm.DeletedAt)。调用 Delete 时 GORM 将 DeletedAt 设为当前时间而非执行 DELETE。所有查询自动追加 WHERE deleted_at IS NULL。使用 db.Unscoped() 可跳过此条件查询到已删除记录,Unscoped().Delete() 则执行硬删除。
Q2:N+1 查询问题是什么?GORM 如何解决?
查询主表 N 条记录后,遍历每条记录单独查询关联表,产生 1 + N 次 SQL。GORM 通过 Preload("Orders") 在一次查询中批量加载关联数据(生成两条 SQL:一条查主表,一条用 WHERE id IN (...) 查关联),也可用 Joins("Orders") 生成单条 JOIN SQL。Preload 适合一对多场景,Joins 适合过滤关联条件的场景。
Q3:GORM 钩子的执行顺序是什么?
以 Create 为例:BeforeSave → BeforeCreate → 执行插入 → AfterCreate → AfterSave。如果 BeforeSave 或 BeforeCreate 返回 error,整个流程中断。注意:批量操作(如 CreateInBatches)中钩子对每条记录单独触发。
Q4:GORM 的事务有几种用法?
三种:
- 闭包事务:
db.Transaction(func(tx *gorm.DB) error { ... }),最推荐,返回 error 自动回滚 - 手动事务:
db.Begin()→tx.Commit()/tx.Rollback(),需要自行处理 panic - 嵌套事务:通过
SavePoint/RollbackTo实现,适用于需要部分回滚的场景
Q5:GORM 的 First 和 Find 有什么区别?
First 查询一条记录(追加 LIMIT 1),记录不存在时返回 ErrRecordNotFound;Find 查询多条记录,记录不存在时不报错,只返回空切片。如果只需要一条数据,用 First 更明确。
GORM 的局限与注意事项
- 反射开销:基于反射的字段映射在高频写入场景下有性能损耗,极端场景可考虑
sqlx或sqlc - 复杂 SQL 受限:窗口函数、CTE 等复杂查询需要手写原生 SQL(
db.Raw()) - 自动迁移只增不删:
AutoMigrate不会删除列或修改列类型,生产环境应使用专业迁移工具 - 软删除陷阱:
Unique约束与软删除冲突——软删除的记录仍占唯一索引位,需用复合唯一索引或WHERE条件索引