模型嵌套

多个模型有相同字段时,重复定义很烦。GORM 支持结构体嵌套,可以把公共字段提取出来复用。

基本嵌套

type BaseModel struct {
    ID        uint `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

type User struct {
    BaseModel
    Name  string
    Email string
}

type Post struct {
    BaseModel
    Title   string
    Content string
}

UserPost 都会继承 BaseModel 的字段。

gorm.Model

GORM 内置的 gorm.Model

type Model struct {
    ID        uint `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

直接嵌入使用:

type User struct {
    gorm.Model
    Name string
}

嵌入标签

embedded

显式声明嵌入:

type User struct {
    BaseModel `gorm:"embedded"`
    Name      string
}

默认就是嵌入的,embedded 标签通常省略。

embeddedPrefix

给嵌入字段加前缀:

type User struct {
    BaseModel `gorm:"embedded;embeddedPrefix:base_"`
    Name      string
}

生成的表结构:

CREATE TABLE users (
    base_id bigint unsigned,
    base_created_at datetime,
    base_updated_at datetime,
    name longtext
);

多层嵌套

type TimestampModel struct {
    CreatedAt time.Time
    UpdatedAt time.Time
}

type BaseModel struct {
    TimestampModel
    ID uint `gorm:"primaryKey"`
}

type User struct {
    BaseModel
    Name string
}

嵌入与关联

嵌入结构体中的关联字段:

type BaseEntity struct {
    CreatedBy uint
    Creator   User `gorm:"foreignKey:CreatedBy"`
}

type Post struct {
    BaseEntity
    Title string
}

覆盖嵌入字段

可以覆盖嵌入字段:

type BaseModel struct {
    ID uint `gorm:"primaryKey"`
}

type User struct {
    BaseModel
    ID   uint `gorm:"primaryKey;autoIncrement:false"`  // 覆盖
    Name string
}

指针嵌入

嵌入指针类型:

type User struct {
    *BaseModel
    Name string
}

注意:指针嵌入时,如果嵌入结构为 nil,所有字段都是 NULL。

值类型嵌入

嵌入非指针:

type User struct {
    BaseModel  // 值类型嵌入
    Name string
}

推荐值类型嵌入,避免 nil 问题。

实际应用

公共审计字段

type AuditModel struct {
    CreatedAt time.Time
    CreatedBy uint
    UpdatedAt time.Time
    UpdatedBy uint
}

type Order struct {
    ID     uint `gorm:"primaryKey"`
    AuditModel
    Amount float64
}

软删除基类

type SoftDeleteModel struct {
    ID        uint `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

type Product struct {
    SoftDeleteModel
    Name  string
    Price float64
}

多租户基类

type TenantModel struct {
    ID        uint `gorm:"primaryKey"`
    TenantID  uint `gorm:"index"`
    CreatedAt time.Time
}

type Order struct {
    TenantModel
    Amount float64
}

注意事项

方法继承

嵌入结构体的方法会被继承:

func (m BaseModel) BeforeCreate(tx *gorm.DB) error {
    // 会被 User 继承
}

type User struct {
    BaseModel
    Name string
}

类型判断

嵌入后,类型关系:

user := User{}
_, ok := interface{}(user).(BaseModel)  // false
_, ok := interface{}(&user).(*BaseModel) // false

嵌入不是继承,是组合。

JSON 序列化

嵌入字段在 JSON 中会展开:

user := User{
    BaseModel: BaseModel{ID: 1},
    Name: "张三",
}
// JSON: {"ID":1,"Name":"张三"}

如果想要嵌套结构:

type User struct {
    Base *BaseModel `json:"base"`
    Name string     `json:"name"`
}

小结

模型嵌套的要点:

  • 用嵌入复用公共字段
  • gorm.Model 提供常用字段
  • embeddedPrefix 添加字段前缀
  • 值类型嵌入优于指针嵌入
  • 嵌入是组合,不是继承

合理使用嵌套可以减少重复代码,让模型定义更清晰。