beforecreate钩子必须使用指针接收者*t,签名严格为func (u *model) beforecreate(tx *gorm.db) error,值接收者不触发且无法修改原结构体;uuid主键需导出并带gorm:"primarykey"标签,id应在钩子中生成而非外部初始化。

BeforeCreate 钩子必须是 *T 类型指针接收者
如果你写成 func (u User) BeforeCreate(...)(值接收者),GORM 完全不会调用它——钩子函数签名必须严格为 func (u *YourModel) BeforeCreate(tx *gorm.DB) error。值接收者无法修改原结构体字段,也无法被 GORM 识别。
UUID 字段需声明为导出字段 + 正确 tag
非导出字段(小写开头)会被 GORM 忽略,UUID 主键必须导出且带 gorm:"primaryKey" 或 gorm:"type:uuid;primary_key"。常见错误是漏掉 primary_key 导致插入时报 “missing primary key”。
type User struct { ID uuid.UUID `gorm:"type:uuid;primaryKey"` Name string }- 别用
id uuid.UUID(小写),GORM 不会读写 - 若数据库用 PostgreSQL,
type:uuid可省略(驱动自动推断),但显式声明更稳妥
生成 UUID 要在 BeforeCreate 中直接赋值,别依赖外部初始化
如果在创建 struct 实例时就手动赋了 ID: uuid.NewV4(),BeforeCreate 还会再执行一次,导致 ID 被覆盖或重复生成。正确做法是:留空 ID,完全交由钩子填充。
示例:
func (u *User) BeforeCreate(tx *gorm.DB) error {
u.ID = uuid.Must(uuid.NewV4())
return nil
}
- 用
uuid.Must(...)简化错误处理(NewV4 不会失败) - 不要在钩子里调
tx.Create(...)或其他 DB 操作——可能引发递归或事务嵌套 - 若用
github.com/google/uuid,注意其UUID是 [16]byte,和satori/go.uuid类型不兼容
关联创建时 BeforeCreate 会被意外触发,需跳过
当父模型带 Locations []Location 关联并执行 db.Create(&user) 时,GORM 会为每个 Location 实例也调用其 BeforeCreate——这会导致本应复用的已有 Location 被强塞新 UUID 插入。
- 解决方法:对关联对象禁用钩子,用
db.Session(&gorm.Session{SkipHooks: true}).Create(&location) - 或者更彻底:只在主模型上定义
BeforeCreate,关联模型不用 UUID 主键(改用自增 + 复合唯一约束) - 切勿在
BeforeCreate里查库判断是否存在——它运行在事务内,查不到自己刚 insert 的记录
gorm:"type:text" 存字符串;而 MySQL 8.0+ 推荐用 binary(16) 存压缩后的字节,这时就得用 uuid.Must(uuid.NewV4()).Bytes() 并配 type:binary(16)。字段类型错,钩子填得再准也没用。











