查询构建模块原理说明

基于1.31 本文档深入解析查询构建模块的设计原理和核心原理,涵盖链式 API、条件构建、执行流程等核心内容。


一、设计原理

1.1 模块定位

在整体架构中的位置

查询构建模块是 GORM 对外提供的主要 API 层,是用户交互的主要界面,位于回调系统之上,协调子句系统和 Schema 模块完成查询构建和执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────┐
│ 用户代码 │
│ db.Model(&User{}).Where("age > ?", 18).Find(&users) │
└────────────────────────┬────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ 链式 API (Chainable API) │
│ Where() → Select() → Order() → Limit() → ... │
└────────────────────────┬────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ 终结 API (Finisher API) │
│ Find() / First() / Create() / ... │
└────────────────────────┬────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ 回调系统执行 │
│ Before Query → Build → Execute → After Query │
└────────────────────────┬────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ 子句系统构建 SQL │
│ (调用 Clause.Build() 生成 SQL 和参数) │
└─────────────────────────────────────────────────────────────┘

核心价值

查询构建模块的核心价值在于:

  1. 流畅性: 提供链式调用,代码读起来像自然语言
  2. 类型安全: 通过方法重载和泛型提供类型检查
  3. 延迟执行: 只在调用 Finisher 时才真正执行查询
  4. 组合性: 可以任意组合查询条件

1.2 设计目的

问题 1: 如何提供流畅的链式调用体验?

  • 挑战: 每个方法需要返回合适的类型以支持继续链式调用
  • 挑战: 需要在链式调用中维护查询状态
  • 解决方案: 每个方法返回新的 DB 实例,共享或克隆 Statement

问题 2: 如何支持多种形式的条件输入?

  • 挑战: 开发者可能用字符串、map、结构体等方式表示条件
  • 挑战: 需要将不同形式转换为统一的内部表示
  • 解决方案: BuildCondition 方法统一处理各种输入

问题 3: 如何保证执行的时机正确?

  • 挑战: 需要区分配置阶段和执行阶段
  • 挑战: 需要在正确的时机构建 SQL
  • 解决方案: 链式 API 只配置,Finisher API 触发执行

1.3 结构安排依据

4 天学习时间的分配逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Day 1: 链式 API 设计
目标: 理解流畅调用的实现原理
重点: getInstance()、clone、Statement 共享

Day 2: 条件构建系统
目标: 理解各种条件形式的处理
重点: BuildCondition、Expression 构建

Day 3: Finisher API
目标: 理解查询执行的完整流程
重点: 回调链、SQL 构建、结果扫描

Day 4: 高级特性
目标: 掌握 Preload、Joins、Scopes
重点: 预加载机制、连接查询、作用域复用

由外而内的学习路径

1
2
3
4
5
6
7
8
9
10
11
第 1 层: API 层 (用户看到的)
db.Where().Order().Limit().Find()
└─ 理解方法签名和返回值

第 2 层: 机制层 (如何工作的)
getInstance() → clone → Statement
└─ 理解实例创建和状态管理

第 3 层: 原理层 (为什么这样设计)
不变性、延迟执行、组合模式
└─ 理解设计决策的权衡

1.4 与其他模块的逻辑关系

依赖关系

  • 依赖 Schema: 获取表名、列名、字段类型
  • 依赖子句系统: 添加和构建子句
  • 依赖回调系统: 执行查询前后的钩子

被依赖关系

  • 被关联查询使用: Preload 通过查询构建实现
  • **被事务处理使用: Tx 也是一个 DB 实例

二、核心原理

2.1 关键概念

概念 1: 链式调用的实现机制

定义: 链式调用是一种编程模式,每个方法返回对象本身或相关对象,允许连续调用多个方法。

GORM 的实现方式:

1
2
3
4
5
6
// 基本模式
func (db *DB) Where(query interface{}, args ...interface{}) *DB {
tx := db.getInstance() // 获取实例
// 配置 tx
return tx // 返回实例,支持继续调用
}

getInstance() 的智能克隆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func (db *DB) getInstance() *DB {
if db.clone > 0 {
tx := &DB{Config: db.Config, Error: db.Error}

if db.clone == 1 {
// 创建新的空 Statement(浅克隆)
tx.Statement = &Statement{
DB: tx,
ConnPool: db.Statement.ConnPool,
Context: db.Statement.Context,
Clauses: map[string]clause.Clause{},
Vars: make([]interface{}, 0, 8),
SkipHooks: db.Statement.SkipHooks,
}
} else {
// 深克隆 Statement
tx.Statement = db.Statement.clone()
tx.Statement.DB = tx
}

return tx
}
return db
}

克隆策略对比:

操作 clone 值 Statement Config 用途
原始 DB 0 共享 共享 基础连接
首次克隆 1 新空 Statement 共享 链式调用
后续克隆 2 克隆 Statement 共享 继续链式调用
Session 1 独立 独立副本 会话隔离
NewDB 1 独立 独立 独立连接

学习要点:

  • 链式调用时 clone == 1,创建新的空 Statement(不克隆原 Statement)
  • 继续链式调用时 clone > 1,才真正克隆 Statement
  • clone 值不会自动递增,而是根据创建方式决定(Session 设为 1,链式调用保持 1 或 2)
  • Config 在链式调用时共享,Session 时创建独立副本

概念 2: Statement 的状态管理

定义: Statement 是查询的执行上下文,包含了构建查询所需的所有状态。

核心状态字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type Statement struct {
*DB // 反向引用

// 模型信息
Model interface{} // 模型结构体
Table string // 表名
Schema *schema.Schema // Schema 元数据

// 目标信息
Dest interface{} // 扫描目标
ReflectValue reflect.Value // 反射值

// 子句集合
Clauses map[string]clause.Clause
BuildClauses []string

// 字段控制
Selects []string
Omits []string

// 关联信息
Joins []join
Preloads map[string][]interface{}

// SQL 构建
SQL strings.Builder
Vars []interface{}
}

状态转换流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
初始状态

│ Model(&User{})

设置 Model、Table、Schema

│ Where("age > ?", 18)

添加 WHERE 子句到 Clauses

│ Order("name")

添加 ORDER BY 子句到 Clauses

│ Limit(10)

添加 LIMIT 子句到 Clauses

│ Find(&users)

执行构建和查询

clone() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
func (stmt *Statement) clone() *Statement {
return &Statement{
DB: stmt.DB,
Table: stmt.Table,
Model: stmt.Model,
Clauses: make(map[string]clause.Clause),
BuildClauses: stmt.BuildClauses,
// 共享引用
Schema: stmt.Schema,
// ... 复制其他字段
}
}

学习要点:

  • Statement 是查询状态的容器
  • clone() 创建浅拷贝,共享 Schema
  • Clauses 是独立的,每个 DB 实例有自己的

概念 3: BuildCondition 条件构建

定义: BuildCondition 是统一处理各种条件形式的入口方法。

支持的输入形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 形式 1: 字符串 + 参数
db.Where("age > ?", 18)
// WHERE age > 18

// 形式 2: map
db.Where(map[string]interface{}{
"age > ?": 18,
"name": "John",
})
// WHERE (age > 18) AND name = 'John'

// 形式 3: 结构体
db.Where(User{Name: "John", Age: 18})
// WHERE name = 'John' AND age = 18

// 形式 4: Expression
db.Where(clause.Expr{
SQL: "age BETWEEN ? AND ?",
Vars: []interface{}{18, 65},
})
// WHERE age BETWEEN 18 AND 65

构建流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func (stmt *Statement) BuildCondition(query interface{}, args ...interface{}) []clause.Expression {
switch v := query.(type) {
case clause.Expression:
return []clause.Expression{v}

case map[string]interface{}:
var exprs []clause.Expression
for key, val := range v {
exprs = append(exprs, stmt.buildExpression(key, val))
}
return exprs

case string:
if len(args) > 0 {
return []clause.Expression{
clause.Expr{SQL: v, Vars: args},
}
}
// 字段名 = 字段值
return []clause.Expression{
clause.NamedExpr{SQL: "? = ?", Vars: []interface{}{clause.Column{Name: v}, v}},
}

default:
// 结构体处理
return stmt.buildStructCondition(v)
}
}

学习要点:

  • 统一入口处理多种形式
  • 不同形式有不同的处理逻辑
  • 最终都转换为 Expression

概念 4: 链式 API 完整实现

定义: 链式 API 是 GORM 对外提供的主要查询接口,包括 Model、Where、Select、Order、Limit 等方法。

源码位置: chainable_api.go

核心方法完整解析:

1. Model() - 设置模型
1
2
3
4
5
6
// Model specify the model you would like to run db operations
func (db *DB) Model(value interface{}) (tx *DB) {
tx = db.getInstance() // 获取新实例
tx.Statement.Model = value // 设置模型
return // 返回新实例
}

功能: 指定要操作的模型结构体
使用场景:

1
2
3
4
5
// 更新所有用户的 name
db.Model(&User{}).Update("name", "hello")

// 如果用户的主键非空,将作为条件更新
db.Model(&user).Update("name", "hello")
2. Where() - 添加 WHERE 条件
1
2
3
4
5
6
7
8
9
// Where add where conditions
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB) {
tx = db.getInstance() // 获取新实例
// 构建条件表达式
if conds := tx.Statement.BuildCondition(query, args...); len(conds) > 0 {
tx.Statement.AddClause(clause.Where{Exprs: conds})
}
return
}

功能: 添加 SQL WHERE 条件
支持的形式:

  • Where("age > ?", 18) - 字符串 + 参数
  • Where(map[string]interface{}{"age": 18}) - map
  • Where(User{Age: 18}) - 结构体
3. Select() - 选择字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Select specify fields that you want when querying, creating, updating
func (db *DB) Select(query interface{}, args ...interface{}) (tx *DB) {
tx = db.getInstance()

switch v := query.(type) {
case []string:
// 字符串数组:直接设置 Selects
tx.Statement.Selects = v
case string:
// 单个字符串:解析为表达式
if len(args) > 0 {
tx.Statement.AddClause(clause.Select{
Distinct: false,
Columns: []clause.Column{{Name: v, Raw: true}},
})
} else if strings.Contains(v, "??") {
// 处理 ?? 占位符
tx.Statement.AddClause(clause.Select{
Distinct: false,
Columns: []clause.Column{{Raw: v}},
})
} else {
// 普通字段名
fields := strings.Fields(v)
for _, field := range fields {
tx.Statement.Selects = append(tx.Statement.Selects, field)
}
}
// ... 其他情况处理
}
return
}
4. Order() - 排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Order specify order when retrieving rows from database
func (db *DB) Order(value interface{}) (tx *DB) {
tx = db.getInstance()

switch v := value.(type) {
case clause.OrderBy:
// 直接使用 OrderBy 子句
tx.Statement.AddClause(v)
case clause.OrderByColumn:
// OrderByColumn
tx.Statement.AddClause(clause.OrderBy{
Columns: []clause.OrderByColumn{v},
})
case string:
// 字符串:解析排序表达式
if v != "" {
tx.Statement.AddClause(clause.OrderBy{
Columns: []clause.OrderByColumn{
{Column: clause.Column{Name: v}, Raw: true},
},
})
}
}
return
}
5. Limit() / Offset() - 分页
1
2
3
4
5
6
7
8
9
10
11
12
13
// Limit specify the number of records to be retrieved
func (db *DB) Limit(limit int) (tx *DB) {
tx = db.getInstance()
tx.Statement.AddClause(clause.Limit{Limit: &limit})
return
}

// Offset specify the number of records to skip before starting to return the records
func (db *DB) Offset(offset int) (tx *DB) {
tx = db.getInstance()
tx.Statement.AddClause(clause.Limit{Offset: offset})
return
}

链式 API 调用流程图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
db.Model(&User{}).Where("age > ?", 18).Order("name").Limit(10).Find(&users)


┌─────────────────────────────────────────────────────────────────┐
│ Model(&User{}) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. getInstance() → 创建新 DB 实例 │ │
│ │ 2. tx.Statement.Model = &User{} │ │
│ │ 3. 设置 Table、Schema │ │
│ └─────────────────────────────────────────────────────────────┘ │


┌─────────────────────────────────────────────────────────────────┐
│ Where("age > ?", 18) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. getInstance() → 创建新 DB 实例 │ │
│ │ 2. BuildCondition("age > ?", 18) → 构建表达式 │ │
│ │ 3. AddClause(clause.Where{Exprs: ...}) │ │
│ └─────────────────────────────────────────────────────────────┘ │


┌─────────────────────────────────────────────────────────────────┐
│ Order("name") │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. getInstance() → 创建新 DB 实例 │ │
│ │ 2. AddClause(clause.OrderBy{Columns: ...}) │ │
│ └─────────────────────────────────────────────────────────────┘ │


┌─────────────────────────────────────────────────────────────────┐
│ Limit(10) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. getInstance() → 创建新 DB 实例 │ │
│ │ 2. AddClause(clause.Limit{Limit: &10}) │ │
│ └─────────────────────────────────────────────────────────────┘ │


┌─────────────────────────────────────────────────────────────────┐
│ Find(&users) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. getInstance() → 创建新 DB 实例 │ │
│ │ 2. tx.Statement.Dest = &users │ │
│ │ 3. 触发回调链执行 │ │
│ │ → Build SQL │ │
│ │ → Execute Query │ │
│ │ → Scan Results │ │
│ └─────────────────────────────────────────────────────────────┘ │


返回结果

概念 5: Finisher API 完整实现

定义: Finisher API 是触发查询执行的终结方法,包括 Find、First、Create、Update、Delete 等。

源码位置: finisher_api.go

核心方法完整解析:

1. Find() - 查询多条记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Find find all records that match given conditions
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB) {
tx = db.getInstance() // 获取新实例
tx.Statement.Dest = dest // 设置扫描目标
tx.Statement.AddClause(clause.Where{}) // 初始化 WHERE 子句

// 如果有额外条件,添加到 WHERE
if len(conds) > 0 {
exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...)
tx.Statement.AddClause(clause.Where{Exprs: exprs})
}

// 调用查询回调执行
return tx.callbacks.Query().Execute(tx)
}

执行流程:

  1. 创建新的 DB 实例
  2. 设置目标变量(用于扫描结果)
  3. 构建条件表达式
  4. 触发 Query 回调链执行
2. First() - 查询单条记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// First find first record that match given conditions, order by primary key
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB) {
tx = db.getInstance()
tx.Statement.Dest = dest
tx.Statement.AddClause(clause.Limit{Limit: clause.Expr{SQL: "1"}})

// 添加主键排序
tx.Statement.AddClause(clause.OrderBy{
Columns: []clause.OrderByColumn{{
Column: clause.Column{Table: tx.Statement.Table, Name: clause.PrimaryKey},
}},
})

if len(conds) > 0 {
exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...)
tx.Statement.AddClause(clause.Where{Exprs: exprs})
}

return tx.callbacks.Query().Execute(tx)
}

特点: 自动添加 LIMIT 1 和主键排序

3. Create() - 创建记录
1
2
3
4
5
6
7
8
9
10
11
12
13
// Create insert the value into database
func (db *DB) Create(value interface{}) (tx *DB) {
tx = db.getInstance()
tx.Statement.Dest = value
tx.Statement.AddClause(clause.Insert{}) // 添加 INSERT 子句

// 设置表名
if tx.Statement.Table == "" {
tx.Statement.Table = tx.Statement.Schema.Table
}

return tx.callbacks.Create().Execute(tx)
}
4. Update() - 更新记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Update update attributes with callbacks, refer: https://gorm.io/docs/update
func (db *DB) Update(column string, value interface{}) (tx *DB) {
tx = db.getInstance()

// 构建更新表达式
tx.Statement.AddClause(clause.Set{
[]clause.Assignment{
{Column: clause.Column{Name: column}, Value: value},
},
})

tx.Statement.AddClause(clause.Where{
Exprs: []clause.Expression{
tx.Statement.BuildCondition(tx.Statement.Model, tx.Statement.clauseExpression(tx.Statement.PrimaryFields)...),
},
})

return tx.callbacks.Update().Execute(tx)
}
5. Delete() - 删除记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Delete delete value match given conditions
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB) {
tx = db.getInstance()

if value != nil {
tx.Statement.Dest = value
tx.Statement.AddClause(clause.Where{
Exprs: []clause.Expression{
tx.Statement.BuildCondition(value),
},
})
}

if len(conds) > 0 {
exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...)
tx.Statement.AddClause(clause.Where{Exprs: exprs})
}

return tx.callbacks.Delete().Execute(tx)
}

Finisher API 执行流程对比:

方法 目标设置 添加子句 回调类型 返回
Find Dest Where Query *DB
First Dest Limit + OrderBy + Where Query *DB
Create Dest Insert Create *DB
Update - Set + Where Update *DB
Delete Dest Where Delete *DB

2.2 理论基础

理论 1: Builder 模式

模式定义: 将复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。

在查询构建中的体现:

1
2
3
4
5
6
7
8
9
传统方式:
user := User{}
db.Select("name").Where("age > ?", 18).Find(&user)

Builder 模式:
query := db.Model(&User{})
query = query.Select("name")
query = query.Where("age > ?", 18)
query.Find(&user)

优势:

  • 分步构建,逻辑清晰
  • 可以复用部分配置
  • 支持条件判断添加

理论 2: 延迟执行模式

模式定义: 延迟执行是指操作不会立即执行,而是在某个合适的时机才真正执行。

在查询构建中的体现:

1
2
3
4
5
6
7
// 配置阶段 - 不执行查询
query := db.Model(&User{}).
Where("age > ?", 18).
Order("name")

// 执行阶段 - 真正执行
query.Find(&users)

延迟执行的好处:

  1. 优化: 可以在执行前收集所有配置,一次性优化
  2. 灵活性: 可以根据条件决定是否添加某个条件
  3. 安全性: DryRun 模式下可以不执行

实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 链式 API 只配置,不执行
func (db *DB) Where(query interface{}, args ...interface{}) *DB {
tx := db.getInstance()
tx.Statement.AddClause(clause.Where{...})
return tx // 只返回,不执行
}

// Finisher 触发执行
func (db *DB) Find(dest interface{}, conds ...interface{}) *DB {
tx := db.getInstance()
tx.Statement.Dest = dest
// ...
return tx.callbacks.Query().Execute(tx) // 执行
}

理论 3: 不可变对象模式

模式定义: 创建后状态不能修改的对象,每次修改返回新对象。

在查询构建中的部分应用:

1
2
3
4
5
6
// 原始 DB 不被修改
db1 := db.Model(&User{})
db2 := db1.Where("age > ?", 18)

// db1 仍然保持原状态
// db2 有新的 Where 条件

优势:

  • 避免意外修改
  • 支持并发使用
  • 易于推理

权衡:

  • 创建新对象有开销
  • 通过共享 Statement 优化

2.3 学习方法

方法 1: 调用追踪法

工具: IDE 的调试功能

步骤:

  1. 设置断点在 Find() 方法
  2. 运行查询
  3. 查看调用栈
  4. 观察每一步的状态变化

关键观察点:

1
2
3
4
5
6
7
// 观察点 1: Where() 中
tx.Statement.Clauses["WHERE"] // 查看 WHERE 子句

// 观察点 2: Find() 中
tx.Statement.Dest // 查看目标
tx.Statement.SQL.String() // 查看生成的 SQL
tx.Statement.Vars // 查看参数

方法 2: 对比实验法

对比不同调用方式的效果:

1
2
3
4
5
6
7
8
9
10
11
// 方式 1: 链式调用
users := []User{}
db.Where("age > ?", 18).Order("name").Find(&users)

// 方式 2: 分步调用
query := db.Model(&User{})
query = query.Where("age > ?", 18)
query = query.Order("name")
query.Find(&users)

// 对比: 两种方式效果相同

对比不同条件形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 字符串形式
db.Where("age > ? AND name = ?", 18, "John")

// map 形式
db.Where(map[string]interface{}{
"age > ?": 18,
"name": "John",
})

// 结构体形式
db.Where(User{Age: 18, Name: "John"})

// 对比: 生成的 SQL 相同

2.4 实施策略

策略 1: 渐进式学习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
第 1 阶段: 简单查询 (Day 1)
db.Find(&users)
db.First(&user)
db.Where("age > ?", 18).Find(&users)

第 2 阶段: 复杂查询 (Day 2)
db.Joins("Company").Find(&users)
db.Preload("Orders").Find(&users)
db.Scopes(AgeGT(18)).Find(&users)

第 3 阶段: 高级查询 (Day 3-4)
子查询、分组、聚合
动态条件构建
性能优化

策略 2: 问题驱动

问题序列:

  1. 链式调用为什么不修改原对象?

    • 测试原对象在调用后的状态
    • 理解不可变性的价值
  2. 如何动态构建查询?

    • 实现带条件的查询构建器
    • 理解延迟执行的优势
  3. 如何优化复杂查询?

    • 分析生成的 SQL
    • 测试不同写法的性能

策略 3: 验证反馈

验证点 1: 链式调用验证

1
2
3
4
5
6
7
8
9
10
11
12
func TestChaining(t *testing.T) {
original := db.Model(&User{})
chained := original.Where("age > ?", 18)

// 验证原对象不变
_, ok1 := original.Statement.Clauses["WHERE"]
assert.False(t, ok1)

// 验证新对象有条件
_, ok2 := chained.Statement.Clauses["WHERE"]
assert.True(t, ok2)
}

四、实战代码示例

4.1 基础查询示例

示例 1: 简单查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"

"gorm.io/driver/mysql"
"gorm.io/gorm"
)

type User struct {
ID uint
Name string
Email string
Age int
}

func main() {
db, _ := gorm.Open(mysql.Open("dsn"), &gorm.Config{})

// 查询所有用户
var users []User
db.Find(&users)

// 条件查询
db.Where("age > ?", 18).Find(&users)

// 单条查询
var user User
db.First(&user, 1)
}

示例 2: 链式查询

1
2
3
4
5
6
7
8
// 复杂链式查询
db.Model(&User{}).
Where("age > ?", 18).
Where("status = ?", "active").
Order("created_at DESC").
Limit(10).
Offset(20).
Find(&users)

4.2 动态查询构建

示例 3: 条件式查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 动态构建查询
query := db.Model(&User{})

// 根据条件动态添加 WHERE
if name := r.URLParam("name"); name != "" {
query = query.Where("name = ?", name)
}

if minAge := r.URLParam("min_age"); minAge != "" {
query = query.Where("age >= ?", minAge)
}

if status := r.URLParam("status"); status != "" {
query = query.Where("status = ?", status)
}

// 执行查询
query.Find(&users)

4.3 高级查询示例

示例 4: 使用 Scopes 复用查询逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义 Scope
func AgeGreaterThan(age int) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("age > ?", age)
}
}

func ActiveUsers(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", "active")
}

// 使用 Scope
db.Scopes(AgeGreaterThan(18), ActiveUsers).Find(&users)

示例 5: 子查询

1
2
3
4
5
6
7
8
9
10
// 使用子查询
var users []User
db.Where("age > (?)", db.Model(&User{}).
Select("AVG(age)").
Where("status = ?", "active")).
SubQuery()).
Find(&users)

// 生成 SQL:
// SELECT * FROM users WHERE age > (SELECT AVG(age) FROM users WHERE status = 'active')

五、最佳实践与故障排查

5.1 查询构建最佳实践

1. 使用 Model 指定表

1
2
3
4
5
// 推荐
db.Model(&User{}).Where("age > ?", 18).Find(&users)

// 不推荐(容易出错)
db.Table("users").Where("age > ?", 18).Find(&users)

2. 避免全局更新

1
2
3
4
5
6
7
8
9
10
// 危险:会更新所有记录
db.Model(&User{}).Update("status", "inactive")

// 安全:添加条件
db.Model(&User{}).Where("status = ?", "active").Update("status", "inactive")

// 或者使用 Session 允许全局更新
db.Session(&gorm.Session{AllowGlobalUpdate: true}).
Model(&User{}).
Update("status", "inactive")

3. 使用 Preload 预加载关联

1
2
3
4
5
6
7
8
9
// N+1 查询问题
users := []User{}
db.Find(&users) // 1 次查询
for _, user := range users {
db.Model(&user).Related(&orders) // N 次查询
}

// 使用 Preload 解决
db.Preload("Orders").Find(&users) // 2 次查询(1 + 1)

5.2 常见问题与解决方案

问题 1: 查询条件不生效

症状: 添加的 WHERE 条件没有生效

原因: 使用了错误的链式调用顺序

1
2
3
4
5
// 错误:Find() 后再调用 Where 不会生效
db.Find(&users).Where("age > ?", 18)

// 正确:先调用 Where,再调用 Find
db.Where("age > ?", 18).Find(&users)

问题 2: 更新失败

症状: Update 没有更新任何记录

原因: 缺少主键或 WHERE 条件

1
2
3
4
5
6
7
8
9
10
// 错误:没有主键
user := User{Name: "John", Age: 30}
db.Model(&user).Update("age", 31) // 不会更新任何记录

// 正确:先查询再更新
db.First(&user, 1)
db.Model(&user).Update("age", 31)

// 或者使用 Where
db.Model(&User{}).Where("name = ?", "John").Update("age", 31)

问题 3: 结构体条件只查询非零值

症状: 使用结构体作为条件时,零值字段被忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
// 问题:Age = 0 会被忽略
db.Where(User{Name: "John", Age: 0}).First(&user)

// 解决方案 1: 使用 map
db.Where(map[string]interface{}{"name": "John", "age": 0}).First(&user)

// 解决方案 2: 使用指针
type User struct {
Name string
Age *int // 使用指针
}
age := 0
db.Where(User{Name: "John", Age: &age}).First(&user)

六、学习验证

6.1 知识自测

基础题

  1. 以下哪个是 Finisher API?

    • A. Where()
    • B. Select()
    • C. Find()
    • D. Order()
  2. 链式 API 的返回值是什么?

    • A. *gorm.DB
    • B. error
    • C. int64
    • D. bool
  3. First() 方法会自动添加什么?

    • A. WHERE
    • B. LIMIT 1
    • C. ORDER BY
    • D. LIMIT 1 和 ORDER BY

进阶题

  1. 以下代码会产生几次查询?

    1
    db.Preload("Orders").Preload("Profile").Find(&users)
    • A. 1 次
    • B. 2 次
    • C. 3 次
    • D. 4 次
  2. 如何实现动态查询?

    • 使用 Scope
    • 使用条件判断
    • 使用 map 作为条件
    • 以上都可以

6.2 实践练习

练习 1: 实现分页查询

1
2
3
4
// 实现 Paginate 函数
func Paginate(db *gorm.DB, page, pageSize int) *gorm.DB {
// TODO: 实现 Limit 和 Offset
}

练习 2: 实现软删除查询

1
2
3
4
// 实现只查询未删除记录的 Scope
func NotDeleted(db *gorm.DB) *gorm.DB {
// TODO: 添加 deleted_at IS NULL 条件
}

三、学习路径建议

3.1 前置知识检查

知识点 要求 检验方式
方法链 理解返回值传递 能设计简单链式 API
SQL 基础 熟悉 SELECT/INSERT/UPDATE/DELETE 能写出复杂查询
反射基础 理解反射获取字段值 能遍历结构体字段

3.2 学习时间分配

内容 理论 实践 产出
Day 1: 链式 API 2h 1.5h 调用流程图
Day 2: 条件构建 1.5h 2h 条件形式对比
Day 3: Finisher 2h 1.5h 执行流程图
Day 4: 高级特性 1.5h 2h 优化示例

3.3 学习成果验收

理论验收:

  • 能解释链式调用的实现
  • 能说明 Statement 的作用
  • 能区分 Chainable 和 Finisher API

实践验收:

  • 能构建复杂查询
  • 能优化查询性能
  • 能实现自定义查询方法