206 lines
5.7 KiB
Markdown
206 lines
5.7 KiB
Markdown
|
|
---
|
|||
|
|
tags: [GORM, Go, ORM, 模型, struct tag, 字段映射, 主键, 表名]
|
|||
|
|
create time: 2026-04-28 00:00
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 模型定义
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
模型(Model)是 GORM 操作的核心入口——一个普通的 Go struct 被 GORM「看见」后,就可以直接进行数据库操作。本章讲解如何通过 struct tag、命名约定和接口来自定义 GORM 对模型的解读。
|
|||
|
|
|
|||
|
|
## 什么是 Model?
|
|||
|
|
|
|||
|
|
> [!definition] Model
|
|||
|
|
> Model 是一个包含以下任一条件的 struct:
|
|||
|
|
> 1. 有主键字段
|
|||
|
|
> 2. 定义了 `TableName()` 方法
|
|||
|
|
> 3. 被 `AutoMigrate`、`Create`、`Where` 等方法引用
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type User struct {
|
|||
|
|
ID uint `gorm:"primaryKey"`
|
|||
|
|
Name string `gorm:"size:64;not null"`
|
|||
|
|
}
|
|||
|
|
// 这就是一个最简单的 GORM Model
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 表名规则
|
|||
|
|
|
|||
|
|
### 默认命名策略
|
|||
|
|
|
|||
|
|
GORM 使用 `Table` 方法来推导表名:
|
|||
|
|
|
|||
|
|
| 代码行为 | 结果 |
|
|||
|
|
|----------|------|
|
|||
|
|
| 默认 | struct 名的蛇形复数形式(`User` → `users`) |
|
|||
|
|
| 实现 `TableName() string` | 返回的字符串 |
|
|||
|
|
| 使用 `db.Table("xxx")` | 查询时使用的表名(不会修改 Model 的 TableName) |
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func (User) TableName() string {
|
|||
|
|
return "sys_user" // 显式指定表名
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!tip] 多环境表名
|
|||
|
|
> 不同环境下表名前缀不同?可以用环境变量 + `TableName()` 实现动态切换:
|
|||
|
|
> ```go
|
|||
|
|
> func (User) TableName() string {
|
|||
|
|
> if os.Getenv("ENV") == "test" {
|
|||
|
|
> return "test_sys_user"
|
|||
|
|
> }
|
|||
|
|
> return "sys_user"
|
|||
|
|
> }
|
|||
|
|
> ```
|
|||
|
|
|
|||
|
|
## 字段 Tag 详解
|
|||
|
|
|
|||
|
|
### 完整 Tag 语法
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
gorm:"column:name;type:bigint;not null;default:0;uniqueIndex;index;comment:用户ID"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| Tag 关键字 | 作用 | 示例 |
|
|||
|
|
|------------|------|------|
|
|||
|
|
| `column` | 映射列名 | `column:user_id` |
|
|||
|
|
| `type` | 列类型覆盖 | `type:varchar(128)` |
|
|||
|
|
| `not null` | NOT NULL 约束 | `not null` |
|
|||
|
|
| `default` | 默认值 | `default:'unknown'` |
|
|||
|
|
| `primaryKey` | 主键 | `primaryKey` |
|
|||
|
|
| `autoIncrement` | 自增 | `autoIncrement` |
|
|||
|
|
| `uniqueIndex` | 唯一索引 | `uniqueIndex:idx_name` |
|
|||
|
|
| `index` | 普通索引 | `index:idx_status` |
|
|||
|
|
| `comment` | 注释(MySQL) | `comment:用户名` |
|
|||
|
|
| `<-` | 字段写入控制 | `<-:true` / `<-:false` / `<-:create` / `<-:update` |
|
|||
|
|
| `->` | 只读/只写 | `->:true`(只读) `/ <-:false`(不写入) |
|
|||
|
|
| `-` | 忽略此字段 | `-` |
|
|||
|
|
|
|||
|
|
### 写入权限控制
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type Product struct {
|
|||
|
|
ID uint `gorm:"primaryKey;autoIncrement"`
|
|||
|
|
Name string `gorm:"not null;<-:create"` // 仅创建时可写入
|
|||
|
|
Price float64 `gorm:"not null;<-:true;->:true"` // 读写均可(默认)
|
|||
|
|
CreatedAt time.Time `gorm:"<-:create;->:true;autoTime"` // 仅创建时自动填充
|
|||
|
|
UpdatedAt time.Time `gorm:"<-:update;->:true;autoTime"` // 仅更新时自动填充
|
|||
|
|
ViewCount int `gorm:"->:false;<-:false"` // 完全忽略(非 DB 字段)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!example] 方向记忆法
|
|||
|
|
> `<-` 表示数据**流向数据库**(写入),`->` 表示数据**从数据库流出**(读取)。箭头方向就是数据的方向。
|
|||
|
|
|
|||
|
|
## 字段类型映射
|
|||
|
|
|
|||
|
|
| Go 类型 | 推荐 DB 类型 | 说明 |
|
|||
|
|
|---------|-------------|------|
|
|||
|
|
| `int`, `int64` | BIGINT | GORM 默认以 `int64` 处理 |
|
|||
|
|
| `uint`, `uint64` | BIGINT UNSIGNED | — |
|
|||
|
|
| `string` | VARCHAR(n) | 需手动指定 size |
|
|||
|
|
| `bool` | TINYINT(1) / BOOLEAN | — |
|
|||
|
|
| `float64` | DOUBLE | — |
|
|||
|
|
| `time.Time` | DATETIME / TIMESTAMP | 配合 `autoTime` tag |
|
|||
|
|
| `[]byte` | BLOB / BYTEA | 二进制数据 |
|
|||
|
|
| `json.RawMessage` | JSON | JSON 序列化字段 |
|
|||
|
|
|
|||
|
|
### `autoTime` 自动时间戳
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type Article struct {
|
|||
|
|
ID uint `gorm:"primaryKey"`
|
|||
|
|
Title string `gorm:"size:128;not null"`
|
|||
|
|
CreatedAt time.Time `gorm:"autoTime;createTime"` // 创建时自动填充
|
|||
|
|
UpdatedAt time.Time `gorm:"autoTime;updateTime"` // 更新时自动填充
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!note] createTime / updateTime
|
|||
|
|
> 这两个子 tag 是 GORM v2 的增强功能,配合 `autoTime` 使用。单独 `autoTime` 只更新时间,加上 `createTime` 后才会在插入时设置 CreatedAt。
|
|||
|
|
|
|||
|
|
## 主键策略
|
|||
|
|
|
|||
|
|
### 默认策略:ID uint
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type User struct {
|
|||
|
|
ID uint // GORM 默认将名为 ID 的字段视为主键
|
|||
|
|
Name string
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### UUID 主键
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
import "github.com/google/uuid"
|
|||
|
|
|
|||
|
|
type Order struct {
|
|||
|
|
ID uuid.UUID `gorm:"type:char(36);primaryKey"`
|
|||
|
|
Code string `gorm:"uniqueIndex;not null"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 在 Create 前生成 UUID
|
|||
|
|
order.ID = uuid.New()
|
|||
|
|
db.Create(&order)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 复合主键
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type OrderItem struct {
|
|||
|
|
OrderID uint `gorm:"primaryKey"`
|
|||
|
|
ProductID uint `gorm:"primaryKey"`
|
|||
|
|
Quantity int `gorm:"not null"`
|
|||
|
|
}
|
|||
|
|
// 生成表结构:PRIMARY KEY (order_id, product_id)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!tip] 复合主键注意事项
|
|||
|
|
> 使用复合主键时,`First()` 和 `Take()` 将无法工作(因为它们期望单主键),必须使用 `Where()` 精确定位。
|
|||
|
|
|
|||
|
|
## 自定义类型作为字段
|
|||
|
|
|
|||
|
|
当内置类型不够用时,可以实现 GORM 接口:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type MyType int
|
|||
|
|
|
|||
|
|
func (m MyType) GORMDataType() string {
|
|||
|
|
return "INT"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m MyType) Value() (driver.Value, error) {
|
|||
|
|
return int(m), nil // ScannerValuer 接口的 Value 方法
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 模型设计流程图
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
A[定义 struct] --> B{有无 ID 字段?}
|
|||
|
|
B -->|有| C["ID 作主键<br/>uint/int64"]
|
|||
|
|
B -->|无| D{自定义 TableName?}
|
|||
|
|
D -->|实现了| E[按 TableName 返回值]
|
|||
|
|
D -->|未实现| F["struct 名蛇形复数"]
|
|||
|
|
C --> G[添加字段 tag]
|
|||
|
|
E --> G
|
|||
|
|
G --> H{需要特殊类型?}
|
|||
|
|
H -->|是| I[实现 GORMDataType / Valuer]
|
|||
|
|
H -->|否| J[完成]
|
|||
|
|
I --> J
|
|||
|
|
|
|||
|
|
style A fill:#4FC08D,color:#fff
|
|||
|
|
style C fill:#3B82F6,color:#fff
|
|||
|
|
style I fill:#EAB308,color:#fff
|
|||
|
|
style J fill:#A0AEC0,color:#fff
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
|
|||
|
|
- [[01-安装与初始化]]
|
|||
|
|
- [[03-CRUD 操作]]
|
|||
|
|
- [[12-自定义字段类型]]
|