Skip to content
Go
模型定义

模型定义

GORM 通过 Go 结构体定义数据库模型,利用 Struct Tag 控制字段映射、约束和关联关系。


gorm.Model — 内嵌基础模型

GORM 提供了一个预定义的模型结构:

// gorm.Model 的定义
type Model struct {
    ID        uint           `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
}
字段 类型 说明
ID uint 自增主键(MySQL)/ 序列主键(PostgreSQL)
CreatedAt time.Time 创建时间,创建时自动设置
UpdatedAt time.Time 更新时间,更新时自动设置
DeletedAt gorm.DeletedAt 软删除标记(NULL 表示未删除)
// 嵌入 gorm.Model
type User struct {
    gorm.Model
    Name string
    Age  int
}

// 等价于:
type User struct {
    ID        uint           `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
    Name      string
    Age       int
}

💡 建议统一嵌入 gorm.Model,省去重复定义。若不需要软删除,可手动定义字段而不嵌入。


约定大于配置

GORM 遵循一系列命名约定,理解这些约定可以免除大量配置:

约定 规则 示例
表名 结构体名转蛇形复数 UserusersUserProfileuser_profiles
列名 字段名转蛇形 UserNameuser_nameIDid
主键 名称为 IDuint 字段 ID uint 自动为主键
创建时间 名称为 CreatedAttime.Time 字段 创建时自动赋值
更新时间 名称为 UpdatedAttime.Time 字段 更新时自动赋值
软删除 名称为 DeletedAtgorm.DeletedAt 字段 删除时设置时间,查询时自动过滤

修改命名策略

import "gorm.io/gorm/schema"

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    NamingStrategy: schema.NamingStrategy{
        TablePrefix:   "t_",     // 表名前缀 → "t_users"
        SingularTable: true,     // 单数表名 → "user" 而非 "users"
        NoLowerCase:   true,     // 保持大写 → "User"
        NameReplacer:  strings.NewReplacer("CID", "Cid"), // 自定义替换
    },
})

字段标签(Struct Tags)完整参考

字段标签写在结构体字段的 gorm tag 中,使用分号 ; 分隔多个属性:

type User struct {
    ID        uint   `gorm:"primaryKey;autoIncrement;comment:用户ID"`
    Email     string `gorm:"type:varchar(100);uniqueIndex;not null;comment:邮箱"`
    Name      string `gorm:"size:100;default:'';comment:昵称"`
    Age       int    `gorm:"default:0;check:age >= 0;comment:年龄"`
    Balance   float64 `gorm:"type:decimal(10,2);default:0.00"`
    IsActive  bool   `gorm:"default:true"`
    Metadata  string `gorm:"type:json;serializer:json"`
    CreatedAt int64  `gorm:"autoCreateTime:milli"` // 毫秒时间戳
    UpdatedAt int64  `gorm:"autoUpdateTime:milli"`
}

字段级别标签

标签 说明 示例
column:name 指定列名 column:user_name
type:sqlType 指定 SQL 类型 type:varchar(255)type:texttype:decimal(10,2)
size:n 列大小 size:100(相当于 varchar(100)
primaryKey 声明为主键
autoIncrement 自增(MySQL)
unique 唯一约束
uniqueIndex 唯一索引
index 普通索引
not null 非空约束
default:val 默认值 default:''default:0default:CURRENT_TIMESTAMP
comment:text 字段注释 comment:用户手机号
check:expr CHECK 约束 check:age >= 0
<- 只读(不写入) <-:false(从不写入);<-:create(仅创建时写);<-:update(仅更新时写)
-> 只写(不读取) ->:false(从不读取)
- 忽略字段 不映射到数据库
autoCreateTime 创建时自动设置时间 autoCreateTime(秒), autoCreateTime:nano, autoCreateTime:milli
autoUpdateTime 更新时自动设置时间 autoUpdateTime(秒), autoUpdateTime:nano, autoUpdateTime:milli
embedded 嵌入结构体
embeddedPrefix:xx 嵌入结构体列名前缀 embeddedPrefix:addr_
serializer:json JSON 序列化器 serializer:jsonserializer:gobserializer:unixtime

关联标签

标签 说明
foreignKey:Field 指定外键字段
references:Field 指定被引用的字段
polymorphic:Field 多态关联的类型字段名
polymorphicValue:val 多态关联的类型值
many2many:table 多对多中间表名
joinForeignKey:Field 多对多关联外键
joinReferences:Field 多对多引用
constraint:OnUpdate:xxx,OnDelete:xxx 外键约束行为

💡 标签大小写不敏感PRIMARYKEY 等同于 primaryKey。值需要区分大小写的地方使用 correct case。


字段读写权限控制

type User struct {
    ID        uint   `gorm:"primaryKey"`
    Name      string `gorm:"<-"`           // 允许读写(默认)
    Password  string `gorm:"<-:false"`     // 禁止写入(从 DB 读,不写回)
    Secret    string `gorm:"->:false"`     // 禁止读取(写入 DB,不读出)
    Internal  string `gorm:"-"`            // 完全忽略
    Token     string `gorm:"<-:create"`    // 仅创建时写入
    UpdatedBy string `gorm:"<-:update"`    // 仅更新时写入
}
// <-:false 的典型使用场景:密码哈希,只从数据库读取但通过其他方式写入
// 更新时 Password 字段不会被 Updates 覆盖
db.Model(&user).Updates(User{Name: "新名字", Password: "xxx"}) // Password 忽略

🚨 陷阱<-:falseCreate 时也会被忽略。如果创建时需要写入而更新时不需要,使用 <-:create


时间字段类型详解

使用 time.Time

type User struct {
    ID        uint      `gorm:"primaryKey"`
    CreatedAt time.Time // 秒级精度,创建时自动设置
    UpdatedAt time.Time // 秒级精度,更新时自动设置
    DeletedAt gorm.DeletedAt `gorm:"index"` // 软删除
}

使用时间戳(int64)

type User struct {
    ID        uint  `gorm:"primaryKey"`
    CreatedAt int64 `gorm:"autoCreateTime:milli"` // 毫秒时间戳
    UpdatedAt int64 `gorm:"autoUpdateTime:milli"` // 毫秒时间戳
}

自定义时间格式

type User struct {
    Birthday *time.Time `gorm:"type:date"`          // 日期
    LoginAt  time.Time  `gorm:"type:datetime(3)"`   // 毫秒精度
    Extra    time.Time  `gorm:"type:timestamp"`      // 时间戳
}
Go 类型 MySQL 默认映射 PostgreSQL 默认映射
time.Time datetime(3) timestamptz
time.Time + type:date date date
time.Time + type:time time time
*time.Time datetime(3) NULL timestamptz NULL
int64 + autoCreateTime bigint bigint
gorm.DeletedAt datetime(3) NULL timestamptz NULL

🚨 陷阱:PostgreSQL 中 time.Time 默认映射到 timestamptz(带时区)。如果需要不带时区的时间戳,使用 type:timestamp


嵌入结构体

// 嵌入结构体(embedded: 展开字段)
type Audit struct {
    CreatedBy string `gorm:"size:100;comment:创建人"`
    UpdatedBy string `gorm:"size:100;comment:更新人"`
}

type User struct {
    gorm.Model
    Name string
    Audit // 字段展开到 users 表
}
// 生成列:id, created_at, updated_at, deleted_at, name, created_by, updated_by

type User2 struct {
    gorm.Model
    Name  string
    Audit Audit `gorm:"embedded;embeddedPrefix:audit_"` // 带前缀
}
// 生成列:..., audit_created_by, audit_updated_by

// 指针嵌入
type User3 struct {
    gorm.Model
    Name  string
    Audit *Audit `gorm:"embedded"` // NULL 可选的嵌入
}

类型映射对照表

MySQL

Go 类型 MySQL 类型
bool tinyint(1)
int, uint bigint
int8 tinyint
int16 smallint
int32 int
int64 bigint
uint8 tinyint unsigned
uint16 smallint unsigned
uint32 int unsigned
uint64 bigint unsigned
float32 float
float64 double
string(无 size) longtext
string(size:255) varchar(255)
[]byte longblob
time.Time datetime(3)
*time.Time datetime(3) NULL
gorm.DeletedAt datetime(3) NULL

PostgreSQL

Go 类型 PostgreSQL 类型
bool boolean
int, uint bigint
int16 smallint
int32 integer
int64 bigint
float32 real
float64 double precision
string text
time.Time timestamptz
*time.Time timestamptz NULL
[]byte bytea
gorm.DeletedAt timestamptz NULL

💡 使用 type 标签可覆盖默认映射。比如 string 字段想存为 text 而非 varchar(255),使用 gorm:"type:text"


默认值处理

type User struct {
    ID     uint   `gorm:"primaryKey"`
    Name   string `gorm:"default:未命名"`
    Age    int    `gorm:"default:18"`
    IsVIP  bool   `gorm:"default:false"`    // 注意:false 是零值,需特殊处理
    Status string `gorm:"default:active"`
    Bio    string `gorm:"default:NULL"`     // NULL 默认值
}

🔬 深入原理:Go 零值(0, "", false)与数据库默认值的交互:

// 结构体零值字段不会被写入 INSERT(让数据库默认值生效)
db.Create(&User{Name: "张三"})
// INSERT INTO users (name) VALUES ('张三')
// Age 用数据库默认值 18,IsVIP 用 false

// 要显式写入零值,使用指针类型或 Select
db.Select("Age", "IsVIP").Create(&User{Name: "张三"})
// INSERT INTO users (name, age, is_vip) VALUES ('张三', 0, false)
// 推荐:使用指针表示"可能为零值"的字段
type User struct {
    Age   *int  `gorm:"default:18"` // nil → 用默认值,非 nil → 写入具体值
    IsVIP *bool `gorm:"default:false"`
}

age := 0
db.Create(&User{Name: "张三", Age: &age}) // 显式写入 Age=0
db.Create(&User{Name: "李四"})             // Age 用数据库默认值

💡 最佳实践:对于有默认值的数值/布尔字段,使用指针类型 *int/*bool,这样 nil 表示"使用默认值",非 nil 表示"显式设值"(包括零值)。


自定义表名

// 方式一:实现 Tabler 接口(优先级最高)
func (User) TableName() string {
    return "sys_users"
}

// 方式二:临时指定表名
db.Table("sys_users").Find(&users)

// 方式三:通过 NamingStrategy 全局设置(见上文)

💡 TableName() 方法优先级最高,适合固定命名规则(如带模块前缀的表名)。


列名自定义

type User struct {
    ID        uint   `gorm:"primaryKey;column:user_id"` // 自定义列名
    UserName  string `gorm:"column:username"`            // 自定义列名
    PassWord  string `gorm:"column:pass_word"`           // 与蛇形命名一致,可省略
}

常见陷阱汇总

陷阱 说明
🚨 表名复数 默认 Userusers,若数据库中为 user,需设置 SingularTable: true
🚨 零值不更新 Updates(struct) 忽略零值字段,用 Updates(map)Select
🚨 大 string 字段 未指定 sizestring → MySQL 中为 longtext,应显式 size:255
🚨 CreatedAt 精度 默认 datetime(3)(毫秒),若数据库列是 datetime(秒),会报类型不匹配错误
🚨 保留字冲突 列名如 ordergroup 是 SQL 保留字,应用 column:order 并加引号引用
🚨 外键约束 GORM 默认在迁移时创建物理外键,生产环境大多禁用