3、字段约束标签:not null、unique、default、comment 标签的使用与场景
在 GORM 的契约校验体系中,字段约束标签是定义数据完整性的第一道防线。它们直接映射到数据库表结构的 DDL 约束,在模型定义阶段即声明字段的“行为契约”。本章将深入剖析 not null、unique、default 和 comment 四个核心标签的语法、校验逻辑及典型应用场景。
3.1 标签概览与作用对比
这四个标签分别控制字段的空值规则、唯一性、默认值以及文档注释。下表从声明位置、数据库影响、校验时机三个维度进行对比:
| 标签 | 声明位置 | 数据库 DDL 影响 | GORM 校验时机 | 典型场景 |
|---|---|---|---|---|
not null |
字段 tag 中 | 生成 NOT NULL 约束 |
创建/更新记录时 | 必填业务字段(用户名、邮箱) |
unique |
字段 tag 中 | 生成 UNIQUE 索引 |
创建/更新记录时(数据库层) | 唯一标识(身份证号、手机号) |
default |
字段 tag 中 | 生成 DEFAULT 值 |
仅数据库插入时生效 | 状态字段、创建时间、计数字段 |
comment |
字段 tag 中 | 生成 COMMENT 注释 |
仅迁移时写入元数据 | 字段含义说明、枚举值解释 |
not null 和 unique 是“强约束”——GORM 在应用层和数据库层都会参与校验;default 是“弱约束”——仅由数据库在插入时填充;comment 是“元数据约束”——不影响数据逻辑,只提升可维护性。
3.2 not null:非空约束
not null 标签确保字段在写入数据库时不能为 NULL。GORM 在 Create 和 Update 操作中会进行零值检测:如果字段值为对应类型的零值(如 0、""、false),且未显式设置值,GORM 会返回错误。
type User struct {
ID uint `gorm:"primaryKey"`
Username string `gorm:"type:varchar(50);not null"`
Email string `gorm:"type:varchar(100);not null;unique"`
Age int `gorm:"default:18;not null"`
}
使用场景:
- 业务必填字段:用户名、密码哈希值、手机号等不允许为空的字段。
- 关联外键:当外键字段必须指向有效父记录时,应标记
not null。 - 逻辑主键组成部分:联合主键中的每个字段都应是非空的。
NULL 不同。例如 int 类型的零值 0 会被 GORM 视为“未设置”,如果同时标记 not null 且没有 default,插入零值时会报错。解决方案:要么使用 *int 指针类型,要么配合 default 标签。
3.3 unique:唯一约束
unique 标签在数据库层面创建唯一索引,确保字段值在整张表中不重复。GORM 在迁移时会自动生成 UNIQUE 索引,并在插入/更新时由数据库抛出唯一冲突错误。
type Product struct {
ID uint `gorm:"primaryKey"`
Code string `gorm:"type:varchar(32);unique;not null"`
Name string `gorm:"type:varchar(100)"`
}
使用场景:
- 业务唯一标识:身份证号、护照号、订单号。
- 登录凭证:邮箱、手机号(通常与
not null联用)。 - 联合唯一:通过
uniqueIndex标签实现多字段组合唯一。
unique 标签会自动创建 B-tree 索引,对查询性能有正向作用。但过多唯一索引会降低写入性能,建议只在高频查询或强业务唯一性字段上使用。
3.4 default:默认值
default 标签为字段指定数据库层面的默认值。当插入记录时未显式提供该字段值,数据库会自动填充默认值。GORM 在迁移时会将默认值写入 DDL,但在应用层不会进行默认值填充——这是与 not null 的重要区别。
type Order struct {
ID uint `gorm:"primaryKey"`
Status string `gorm:"type:varchar(20);default:pending"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
Quantity int `gorm:"default:1"`
IsDeleted bool `gorm:"default:false"`
}
使用场景:
- 状态枚举:订单状态、用户状态等具有初始值的字段。
- 时间戳:创建时间、更新时间(配合
CURRENT_TIMESTAMP)。 - 计数/开关:点赞数默认
0、是否删除默认false。
default 标签时,GORM 在 Create 操作中会忽略该字段的零值,让数据库填充默认值。但如果显式设置了非零值,则会使用设置值。这一行为在批量插入时尤其需要注意。
3.5 comment:字段注释
comment 标签用于在数据库表结构中添加字段级别的注释。它不影响任何数据操作,但极大地提升了数据库的可维护性和团队协作效率。GORM 在 AutoMigrate 时会自动将注释写入 COMMENT 子句。
type Employee struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"type:varchar(50);not null;comment:员工姓名"`
Gender string `gorm:"type:char(1);default:M;comment:性别 M-男 F-女"`
Level int `gorm:"default:1;comment:职级 1-初级 2-中级 3-高级"`
Email string `gorm:"type:varchar(100);comment:工作邮箱(可选)"`
}
使用场景:
- 枚举值说明:在注释中写明字段的取值范围和含义。
- 业务逻辑提示:解释字段的计算规则或来源(如“最后登录时间”、“累计积分”)。
- 团队协作:新成员通过
SHOW FULL COLUMNS即可理解字段用途,减少沟通成本。
3.6 标签组合与校验流程
实际项目中,这四个标签经常组合使用。下面的 SVG 图展示了 GORM 在创建记录时,如何依次处理这些约束标签:
3.7 综合示例:用户模型
以下是一个综合运用所有四个标签的用户模型示例,展示了实际项目中的典型配置:
type User struct {
ID uint `gorm:"primaryKey;comment:用户唯一标识"`
Username string `gorm:"type:varchar(32);not null;unique;comment:登录用户名"`
PasswordHash string `gorm:"type:char(60);not null;comment:bcrypt 密码哈希"`
Email string `gorm:"type:varchar(100);not null;unique;comment:电子邮箱"`
Phone string `gorm:"type:varchar(20);unique;comment:手机号(可选)"`
Age int `gorm:"default:0;comment:年龄 0-未知"`
Status int `gorm:"default:1;not null;comment:状态 1-正常 2-冻结 3-删除"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP;comment:创建时间"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP;comment:更新时间"`
}
标签组合策略:
- 必填+唯一:
Username、Email同时使用not null和unique,确保既有值又不会重复。 - 必填+默认值:
Status使用not null配合default:1,保证状态字段永远不为空且有初始值。 - 可选+唯一:
Phone只使用unique不使用not null,允许用户暂不绑定手机号。 - 全字段注释:每个字段都添加
comment,便于数据库直接查看字段含义。
comment 标签,即使字段名已经自解释。注释中应包含:字段的业务含义、取值范围、特殊规则(如“仅管理员可修改”)。这能显著降低数据库运维和新人上手成本。
3.8 常见错误与排查
下表列出了使用字段约束标签时的典型错误及解决方案:
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
插入零值时报 not null 错误 |
字段标记了 not null 但未设置 default |
添加 default 标签,或使用指针类型 |
| 唯一冲突错误 | 插入重复值到 unique 字段 |
先查询再插入,或使用 FirstOrCreate |
| 默认值未生效 | GORM 在应用层显式传入了零值 | 使用 map[string]interface{} 时省略该字段 |
| 注释未写入数据库 | 未执行 AutoMigrate 或使用原始 SQL |
确保通过 GORM 迁移,或手动执行 ALTER TABLE |