创建记录时,GORM 会依次触发 BeforeSave、BeforeCreate、AfterCreate、AfterSave 钩子。
创建记录前执行:
func (u *User) BeforeCreate(tx *gorm.DB) error {
u.UUID = uuid.New().String()
return nil
}
创建记录后执行:
func (u *User) AfterCreate(tx *gorm.DB) error {
return tx.Create(&UserProfile{
UserID: u.ID,
}).Error
}
保存前执行,创建和更新都会触发:
func (u *User) BeforeSave(tx *gorm.DB) error {
u.UpdatedAt = time.Now()
return nil
}
保存后执行,创建和更新都会触发:
func (u *User) AfterSave(tx *gorm.DB) error {
return updateCache(tx, u)
}
创建记录时的完整顺序:
BeforeSave
↓
BeforeCreate
↓
执行 INSERT
↓
AfterCreate
↓
AfterSave
钩子在同一个事务中执行:
func (u *User) AfterCreate(tx *gorm.DB) error {
order := Order{
UserID: u.ID,
Status: "pending",
}
if err := tx.Create(&order).Error; err != nil {
return err
}
if err := sendWelcomeEmail(u.Email); err != nil {
return err
}
return nil
}
如果发送邮件失败,整个事务回滚,用户和订单都不会创建。
根据条件决定是否执行:
func (u *User) BeforeCreate(tx *gorm.DB) error {
if u.Role == "" {
u.Role = "user"
}
if u.Role == "admin" {
var count int64
tx.Model(&User{}).Where("role = ?", "admin").Count(&count)
if count >= 10 {
return errors.New("管理员数量已达上限")
}
}
return nil
}
在钩子中修改字段值:
func (u *User) BeforeCreate(tx *gorm.DB) error {
u.Name = strings.TrimSpace(u.Name)
u.Email = strings.ToLower(u.Email)
if u.Password != "" {
hashed, err := bcrypt.GenerateFromPassword([]byte(u.Password), 10)
if err != nil {
return err
}
u.Password = string(hashed)
}
return nil
}
从钩子中获取请求上下文:
func (u *User) BeforeCreate(tx *gorm.DB) error {
ctx := tx.Statement.Context
if userID, ok := ctx.Value("user_id").(uint); ok {
u.CreatedBy = userID
}
return nil
}
// 使用
ctx := context.WithValue(context.Background(), "user_id", uint(1))
db.WithContext(ctx).Create(&user)
批量创建时,每个记录都会触发钩子:
users := []User{
{Name: "用户1"},
{Name: "用户2"},
}
db.Create(&users)
BeforeCreate 会执行两次。
但 CreateInBatches 可能不同:
db.CreateInBatches(users, 100)
分批创建时,每批都会触发钩子。
某些场景需要跳过钩子:
db.Session(&gorm.Session{SkipHooks: true}).Create(&user)
或者用原生 SQL:
db.Exec("INSERT INTO users (name) VALUES (?)", user.Name)
用户注册
func (u *User) BeforeCreate(tx *gorm.DB) error {
if u.Name == "" {
return errors.New("用户名不能为空")
}
if u.Email == "" {
return errors.New("邮箱不能为空")
}
var count int64
tx.Model(&User{}).Where("email = ?", u.Email).Count(&count)
if count > 0 {
return errors.New("邮箱已被注册")
}
if u.Password != "" {
hashed, err := bcrypt.GenerateFromPassword([]byte(u.Password), 10)
if err != nil {
return err
}
u.Password = string(hashed)
}
u.Status = "active"
u.CreatedAt = time.Now()
return nil
}
func (u *User) AfterCreate(tx *gorm.DB) error {
profile := UserProfile{
UserID: u.ID,
}
if err := tx.Create(&profile).Error; err != nil {
return err
}
go sendWelcomeEmail(u.Email)
return nil
}
订单创建
func (o *Order) BeforeCreate(tx *gorm.DB) error {
if o.UserID == 0 {
return errors.New("用户ID不能为空")
}
if len(o.Items) == 0 {
return errors.New("订单项不能为空")
}
o.OrderNo = generateOrderNo()
o.Status = "pending"
var total float64
for _, item := range o.Items {
total += item.Price * float64(item.Quantity)
}
o.TotalAmount = total
return nil
}
func (o *Order) AfterCreate(tx *gorm.DB) error {
for i := range o.Items {
o.Items[i].OrderID = o.ID
}
return tx.Create(&o.Items).Error
}
创建钩子适合处理数据验证、默认值设置、关联创建等逻辑。注意钩子在事务中执行,返回错误会回滚整个操作。