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。
  • 逻辑主键组成部分:联合主键中的每个字段都应是非空的。
避坑提示: Go 的零值语义与数据库 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。
GORM 特殊行为: 当字段使用 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 在创建记录时,如何依次处理这些约束标签:

GORM 创建记录时字段约束校验流程 接收 Create 请求 步骤1:not null 校验 检查零值字段是否标记 not null 步骤2:default 处理 零值字段使用 default 值填充 步骤3:构建 INSERT SQL 包含字段值及 comment 元数据 步骤4:数据库执行 unique 约束由数据库层校验 返回结果 / 错误 校验顺序说明 1. not null 先于 default 2. default 在应用层处理 3. unique 由数据库兜底 4. comment 仅迁移时写入

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