2. 模型定义基础
GORM 的模型定义是构建数据边界的起点。一个结构体(struct)对应一张数据库表,结构体中的字段对应表的列。通过标签(Tag)、表名映射、字段权限控制以及主键策略,开发者可以精确控制 ORM 的行为,确保数据在 Go 与数据库之间的映射既安全又高效。
1. 结构体标签:字段元数据声明
GORM 使用结构体标签(Tag)来声明字段的数据库属性。标签格式为 gorm:"key1:value1;key2:value2",多个键值对之间用分号分隔。以下是核心标签及其用途:
| 标签键 | 说明 | 示例 |
|---|---|---|
column |
指定数据库列名,默认使用蛇形命名 | gorm:"column:user_name" |
type |
指定列的数据类型,如 varchar(100) |
gorm:"type:varchar(100)" |
size |
定义字段大小(如字符串长度) | gorm:"size:255" |
primaryKey |
标记为主键 | gorm:"primaryKey" |
autoIncrement |
启用自增 | gorm:"autoIncrement" |
not null |
非空约束 | gorm:"not null" |
unique |
唯一约束 | gorm:"unique" |
default |
设置默认值 | gorm:"default:0" |
comment |
字段注释 | gorm:"comment:用户昵称" |
index |
创建索引,可指定索引名 | gorm:"index:idx_name" |
最佳实践: 标签中的列名建议使用蛇形命名法(snake_case),与数据库惯例保持一致。同时,
type 和 size 应配合使用,避免数据库自动推断类型导致意外。
type User struct {
ID uint `gorm:"primaryKey;autoIncrement;column:id"`
Name string `gorm:"type:varchar(100);not null;column:user_name;comment:用户名"`
Age int `gorm:"default:18;column:age"`
Email string `gorm:"unique;size:255;column:email"`
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
}
2. 表名映射:结构体到数据库表的桥梁
GORM 默认将结构体名转换为蛇形复数形式作为表名(如 User → users)。当需要自定义表名时,有三种方式:
- 实现
TableName()接口: 在结构体上定义方法,返回自定义表名。 - 使用
Table()方法临时指定: 在查询链中动态切换表。 - 全局表名前缀: 通过
gorm.Config中的NamingStrategy设置前缀。
// 方式一:实现 TableName 接口
func (User) TableName() string {
return "t_user"
}
// 方式二:查询时临时指定表名
db.Table("t_user").Where("id = ?", 1).First(&user)
// 方式三:全局前缀配置
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "t_", // 表名前缀
},
})
提示: 当结构体需要映射到已有数据库表时,优先使用
TableName() 接口。这种方式显式且易于维护,避免在查询链中反复指定表名。
3. 字段权限控制:读写分离与安全边界
GORM 通过标签控制字段的读写权限,防止敏感字段被意外写入或读取。权限控制主要依赖以下标签:
| 标签 | 作用 | 适用场景 |
|---|---|---|
- |
忽略该字段,不参与任何数据库操作 | 计算字段、临时缓存 |
-> |
只读字段,仅用于读取,不写入 | 数据库计算列、视图字段 |
<- |
只写字段,仅用于创建/更新,不读取 | 密码哈希、审计日志 |
<-:create |
仅在创建时写入 | 创建时间戳 |
<-:update |
仅在更新时写入 | 更新时间戳 |
type Account struct {
ID uint `gorm:"primaryKey"`
Username string `gorm:"not null"`
PasswordHash string `gorm:"<-"` // 只写:写入时使用,读取时忽略
ComputedAge int `gorm:"->"` // 只读:从数据库计算列读取
TempToken string `gorm:"-"` // 忽略:仅内存中使用
CreatedAt time.Time `gorm:"<-:create"` // 仅创建时写入
UpdatedAt time.Time `gorm:"<-:update"` // 仅更新时写入
}
安全提醒: 对于密码、密钥等敏感字段,务必使用
gorm:"-" 或 gorm:"<-" 限制其序列化与反序列化路径。同时,在 JSON 序列化时配合 json:"-" 标签,避免数据泄露。
4. 主键与自增ID:数据唯一性的基石
主键是每条记录的唯一标识。GORM 支持多种主键策略,最常用的是自增整数主键和 UUID 主键。自增 ID 由数据库自动生成,适合高并发写入场景;UUID 则适合分布式系统,避免主键冲突。
// 自增整数主键(推荐)
type Product struct {
ID uint `gorm:"primaryKey;autoIncrement;column:id"`
Name string `gorm:"not null"`
Price float64
}
// UUID 主键(分布式场景)
type Order struct {
ID string `gorm:"primaryKey;type:char(36);column:id"`
Amount float64
}
// 复合主键
type OrderItem struct {
OrderID uint `gorm:"primaryKey;column:order_id"`
ProductID uint `gorm:"primaryKey;column:product_id"`
Quantity int
}
自增 ID 的注意事项:
- 类型选择: 推荐使用
uint或int64,避免int在 32 位系统上的溢出风险。 - 数据库差异: MySQL 使用
auto_increment,PostgreSQL 使用SERIAL或IDENTITY,GORM 自动适配。 - 批量插入: 自增 ID 在批量插入后可通过
db.Create(&users)自动回填到结构体中。
5. 综合示例:一个完整的模型定义
将上述知识点整合到一个实际模型中,展示标签、表名、权限和主键的协同使用:
// models/order.go
package models
import (
"time"
"gorm.io/gorm"
)
// Order 订单模型
type Order struct {
ID uint `gorm:"primaryKey;autoIncrement;column:id"`
OrderNo string `gorm:"type:varchar(32);unique;not null;column:order_no;comment:订单号"`
UserID uint `gorm:"not null;index:idx_user_id;column:user_id"`
TotalAmount float64 `gorm:"type:decimal(10,2);default:0.00;column:total_amount"`
Status int8 `gorm:"default:0;column:status;comment:0-待支付 1-已支付 2-已取消"`
InternalNote string `gorm:"-"` // 仅内存使用,不持久化
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index;column:deleted_at"` // 软删除
}
// TableName 自定义表名
func (Order) TableName() string {
return "t_order"
}
设计要点:
- 主键策略: 使用
uint自增主键,适合单库场景。 - 字段权限:
InternalNote使用gorm:"-"完全忽略数据库操作。 - 时间戳:
autoCreateTime和autoUpdateTime自动管理创建/更新时间。 - 软删除: 嵌入
gorm.DeletedAt实现逻辑删除,避免物理删除带来的数据丢失。
通过以上模型定义,开发者可以精确控制每个字段的数据库行为,为后续的关联查询和事务操作打下坚实的数据边界基础。