GORM: Golang 的 ORM 框架

摘要

ORM(Object Relational Mapping,对象关系映射)框架旨在建立面向过程中的对象与数据库中的表(即关系)之间的映射关系,为开发者提供友好的数据库交互方式。GORM 是 Golang 的热门 ORM 框架,支持 MySQL、SQLite 等多种数据源。

约定

GORM 遵循的是 “约定大于配置” 的思想,即遵循约定可以减少配置的数量。这种思想应用广泛,例如 go 中也约定以_test 结尾的文件为测试文件,init() 函数用于模块初始化等。transformers、Spring Boot 框架中也有这种思想的体现。GORM 使用了如下约定:

  • 主键:GORM 使用名为 ID 的字段作为每个模型的默认主键。
  • 表名:默认情况下,GORM 将结构名体称转换为蛇形复数化为表名称。 例如,User 结构体对应数据库中的 users 表。
  • 列名:GORM 自动将数据库中列名的结构体字段名转换为 蛇形
  • 时间戳字段:GORM 使用名为 CreatedAtUpdatedAt 的字段来自动跟踪记录的创建和更新时间。

下面详细介绍。

表名

GORM 约定结构体名的 蛇形复数 作为表名。对于结构体 User,根据约定,其表名为 users,可以通过实现 Tabler 接口指定 “静态” 表名:

1
2
3
4
5
6
7
8
type Tabler interface {
TableName() string
}

// TableName 会将 User 的表名重写为 `profiles`
func (User) TableName() string {
return "profiles"
}

TableName 不支持动态变化,它会被缓存下来以便后续使用。想要使用动态表名,可以使用 Scopes,例如:

1
2
3
4
5
6
7
8
9
10
11
func UserTable(user User) func (tx *gorm.DB) *gorm.DB {
return func (tx *gorm.DB) *gorm.DB {
if user.Admin {
return tx.Table("admin_users")
}

return tx.Table("users")
}
}

db.Scopes(UserTable(user)).Create(&user)

列名

GORM 约定结构体字段名的 蛇形 作为列名,可以通过 gormcolumn tag 指定列名:

1
2
3
4
5
6
7
8
9
10
11
12
type User struct {
ID uint // 列名是 `id`
Name string // 列名是 `name`
Birthday time.Time // 列名是 `birthday`
CreatedAt time.Time // 列名是 `created_at`
}

type Animal struct {
AnimalID int64 `gorm:"column:beast_id"` // 将列名设为 `beast_id`
Birthday time.Time `gorm:"column:day_of_the_beast"` // 将列名设为 `day_of_the_beast`
Age int64 `gorm:"column:age_of_the_beast"` // 将列名设为 `age_of_the_beast`
}

主键

GORM 约定 ID 字段作为表的主键,可以通过 primarykey tag 指定:

1
2
3
4
5
6
7
8
9
10
11
12
type User struct {
ID string // 默认情况下,名为 `ID` 的字段会作为表的主键
Name string
}

// 将 `UUID` 设为主键
type Animal struct {
ID int64
UUID string `gorm:"primaryKey"`
Name string
Age int64
}

时间戳

CreatedAt

GORM 约定 CreatedAt 用于追踪记录的创建时间,并自动初始化。对于有 CreatedAt 字段的模型,创建记录时,如果该字段值为零值,则将该字段的值设为当前时间。

1
2
3
4
5
6
7
db.Create(&user) // 将 `CreatedAt` 设为当前时间

user2 := User{Name: "jinzhu", CreatedAt: time.Now()}
db.Create(&user2) // user2 的 `CreatedAt` 不会被修改

// 想要修改该值,您可以使用 `Update`
db.Model(&user).Update("CreatedAt", time.Now())

UpdatedAt

GORM 约定 UpdatedAt 用于追踪记录的更新时间,并自动赋值。对于有 UpdatedAt 字段的模型,更新记录时,将该字段的值设为当前时间。创建记录时,如果该字段值为零值,则将该字段的值设为当前时间

1
2
3
4
5
6
7
8
9
10
11
db.Save(&user) // 将 `UpdatedAt` 设为当前时间

db.Model(&user).Update("name", "jinzhu") // 会将 `UpdatedAt` 设为当前时间

db.Model(&user).UpdateColumn("name", "jinzhu") // `UpdatedAt` 不会被修改

user2 := User{Name: "jinzhu", UpdatedAt: time.Now()}
db.Create(&user2) // 创建记录时,非零值情况,user2 的 `UpdatedAt` 不会被修改

user3 := User{Name: "jinzhu", UpdatedAt: time.Now()}
db.Save(&user3) // 更新时,即使不是零值,user3 的 `UpdatedAt` 也会被修改为当前时间

模型定义

模型是 Go 中的结构体,字段类型可以是基础 Go 类型、指针或者这些类型的别名。也支持自定义类型,只要实现了来自 database/sql 包的 ScannerValuer 接口。指针的作用是允许 nili 值,与数据库中的 null 对应。

模型定义如下:

1
2
3
4
5
6
7
8
9
10
11
type User struct {
ID uint // Standard field for the primary key
Name string // A regular string field
Email *string // A pointer to a string, allowing for null values
Age uint8 // An unsigned 8-bit integer
Birthday *time.Time // A pointer to time.Time, can be null
MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings
ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields
CreatedAt time.Time // Automatically managed by GORM for creation time
UpdatedAt time.Time // Automatically managed by GORM for update time
}

在该模型中:

  • 基础类型,如 uint,string,uint8 可以直接使用
  • 指针类型,*string,*time.Time 代表字段可以为空
  • sql.NullString,sql.NullTime 来自 database/sql 包,更方便地管理 null 值
  • CreatedAt,UpdatedAt 由 GORM 追踪维护

gorm.Model

GORM 提供了一个预先定义结构体,包含常用字段,可以嵌入在自定义结构体中。对于匿名字段,GORM 会将其字段包含在父结构体中,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"` // index tag 用于根据参数创建索引
}


type User struct {
gorm.Model // 嵌入匿名字段
Name string
}
// 等价于
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
}

GORM 支持的 tag 详见模型定义 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

环境准备

1
2
3
4
// 安装MySQL依赖
go get -u gorm.io/driver/mysql
// 安装Gorm依赖
go get -u gorm.io/gorm

连接数据库

GORM 官方支持的数据库类型有:MySQL, PostgreSQL, SQLite, SQL Server 和 TiDB。GORM 使用 DSN(Data Source Name)标识数据源,通过对应驱动的 Open 方法建立连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import (
"database/sql"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)

// DSN: Data Source Name
// 想要正确的处理 time.Time ,需要带上 parseTime 参数
// 要支持完整的 UTF-8 编码,需要将 charset=utf8 更改为 charset=utf8mb4 查看 此文章 获取详情
// WSL 连接 windows mysql: export HOST_IP=$(grep -oP '(?<=nameserver\ ).*' /etc/resolv.conf)
func formatDSN(username, password, host string, port int, dbName string) string {
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, dbName)
}

func main(){
dsn := formatDSN(username, password, host, port, dbName)
// 连接数据库
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database, error=" + err.Error())
}
}

MySQL 驱动程序提供了一些初始配置,如:

1
2
3
4
5
6
7
8
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
DefaultStringSize: 256, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{})

连接池

GORM 使用 database/sql 来维护连接池

1
2
3
4
5
6
7
8
9
10
sqlDB, err := db.DB()

// 最大空闲连接数量
sqlDB.SetMaxIdleConns(10)

// 最大打开连接数
sqlDB.SetMaxOpenConns(100)

// 连接最大时长
sqlDB.SetConnMaxLifetime(time.Hour)

操作数据库

创建表

GORM 支持 Migration 特性,支持根据 Go Struct 结构自动生成对应的表结构。

1
2
3
4
5
6
// 创建 users 表
db.AutoMigrate(&User{})
// 一次创建User、Product、Order三个结构体对应的表结构
db.AutoMigrate(&User{}, &Product{}, &Order{})
// 可以通过Set设置附加参数,下面设置表的存储引擎为InnoDB
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})

创建记录

1
2
3
4
5
6
7
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

result := db.Create(&user) // 通过数据的指针来创建

user.ID // 返回插入数据的主键
result.Error // 返回 error
result.RowsAffected // 返回插入记录的条数

可以传入切片批量创建多条记录,适用于高效地插入大量记录的场景,GORM 将生成一条 SQL 来插入所有数据,以返回所有主键值,并触发 Hook 方法

1
2
3
4
5
6
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
result := db.Create(users) // 传递切片以插入多行数据
for _, user := range users {
user.ID // 1,2,3
}

GORM 也支持传入 map 创建记录,适用于已有列 - 值 map 关系的场景,省去转为结构体的时空开销。

1
2
3
4
5
6
7
8
9
db.Model(&User{}).Create(map[string]interface{}{
"Name": "jinzhu", "Age": 18,
})

// batch insert from `[]map[string]interface{}{}`
db.Model(&User{}).Create([]map[string]interface{}{
{"Name": "jinzhu_1", "Age": 18},
{"Name": "jinzhu_2", "Age": 20},
})

Hooks

GORM 允许用户定义的钩子有 BeforeSave, BeforeCreate, AfterSave, AfterCreate 创建记录时将调用这些钩子方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()

if u.Role == "admin" {
return errors.New("invalid role")
}
return
}
// 跳过钩子
DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)

DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)

DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(users, 100)

查询记录

GORM 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误。要避免这个错误可以使用 Find 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil

// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)

主键检索

db.First/Find 函数中的第二个参数,传入主键值。

1
2
3
4
5
6
7
8
9
10
11
// 单条
// 整型主键
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
// uuid 字符串 主键
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

// 多条
db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);

也可以设置目标对象的主键值,构成查询条件,例子如下:

1
2
3
4
5
6
7
8
var user = User{ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;

var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;

条件

string 条件

GORM 也支持格式化 sql 作为 where 的条件,例子如下:

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
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

也可以传入结构体 /map 作为条件,例子如下。当传入结构体时,非零字段(0,"",nil)会作为条件,如果要使用零值进行筛选,只可以使用 map 条件。

1
2
3
4
5
6
7
8
9
10
11
12
// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;

// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;

// Slice of primary keys
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);

内联条件

First 中可以类似 Where,进行条件内联。

1
2
3
4
5
6
// Get by primary key if it were a non-integer type
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';

// Plain SQL
db.Find(&user, "name = ?", "jinzhu")

更详细的介绍参见查询 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

更新记录

全部

Save 会保存所有的字段,即使字段是零值

1
2
3
4
5
6
db.First(&user)

user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

Save 是一个组合函数。 如果保存值不包含主键,它将执行 Create,否则它将执行 Update (包含所有字段)。

单列

可以根据条件、model、条件 & model 完成指定列的参数更新。当使用 Update 更新单列时,需要有一些条件,否则将会引起 ErrMissingWhereClause 错误,详见阻止全局更新 。 当使用 Model 方法,并且它有主键值时,主键将会被用于构建条件,例如:

1
2
3
4
5
6
7
8
9
10
11
12
// 根据条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

多列

类似地,支持使用结构体和 map 更新,struct 只更新非零值。

1
2
3
4
5
6
7
8
// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;

// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

Hooks

GORM 支持的 hook 包括:BeforeSave, BeforeUpdate, AfterSave, AfterUpdate. 更新记录时将调用这些方法,查看 Hooks 获取详细信息

1
2
3
4
5
6
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to update")
}
return
}

删除

单条

删除对象需要指定主键,否则会触发 批量删除,例如:

1
2
3
4
5
6
7
// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;

// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";

根据主键删除

GORM 允许通过主键 (可以是复合主键) 和内联条件来删除对象,类似 First 函数。详见 查询 - 内联条件(Query Inline Conditions)

1
2
3
4
5
6
7
8
db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;

db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;

db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);

批量删除

如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录

1
2
3
4
5
6
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";

db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";

可以将一个主键切片传递给 Delete 方法,以便更高效的删除多条记录

1
2
3
4
5
6
var users = []User{{ID: 1}, {ID: 2}, {ID: 3}}
db.Delete(&users)
// DELETE FROM users WHERE id IN (1,2,3);

db.Delete(&users, "name LIKE ?", "%jinzhu%")
// DELETE FROM users WHERE name LIKE "%jinzhu%" AND id IN (1,2,3);

软删除

如果模型包含了 gorm.DeletedAt 字段,则拥有了软删除的能力。当调用 Delete 时,GORM 并不会从数据库中删除该记录,而是将该记录的 DeleteAt 设置为当前时间,而后的一般查询方法将无法查找到此条记录(where 中自动过滤掉)。

1
2
3
4
5
6
7
8
9
10
11
// user's ID is `111`
db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;

// Batch Delete
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;

// Soft deleted records will be ignored when querying
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

可以使用 Unscoped 来查询到被软删除的记录,或者永久删除匹配的记录

1
2
3
4
5
6
7
// 不过滤被软删除的记录
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
// 永久删除
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;

删除标识

保存删除时间戳会占用较多空间,在不关心时间的情况下,一个 0/1 整数亦可以作为软删除的标识。使用 gorm.io/plugin/soft_delete 的一个例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import "gorm.io/plugin/soft_delete"

type User struct {
ID uint
Name string
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
}

// 查询
// SELECT * FROM users WHERE is_del = 0;

// 软删除
// UPDATE users SET is_del = 1 WHERE ID = 1;

Hooks

对于删除操作,GORM 支持 BeforeDeleteAfterDelete Hook,在删除记录时会调用这些方法,查看 Hook 获取详情

1
2
3
4
5
6
func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to delete")
}
return
}

事务

事务对于 ORM 框架来说也不可或缺。为了保持一致性,GORM 默认在事务里进行写操作(创建、更新、删除)。如果没有这方面的要求,可以在初始化时禁用,这将获得大约 30%+ 性能提升。

1
2
3
4
5
6
7
8
9
10
// 全局禁用
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
SkipDefaultTransaction: true,
})

// 持续会话模式
tx := db.Session(&Session{SkipDefaultTransaction: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)

用法

对于一系列操作,通过 Transaction 函数显式启动一个事务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
db.Transaction(func(tx *gorm.DB) error {
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// 返回任何错误都会回滚事务
return err
}

if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}

// 返回 nil 提交事务
return nil
})

手动事务

Gorm 支持直接调用事务控制方法(commit、rollback),例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 开始事务
tx := db.Begin()

// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
tx.Create(...)

// ...

// 遇到错误时回滚事务
tx.Rollback()

// 否则,提交事务
tx.Commit()

性能优化

除了关闭默认事务外,还可以通过以下操作提升性能:

缓存预编译语句

执行任何 SQL 时都创建并缓存预编译语句,可以提高后续的调用速度,详见会话 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

1
2
3
4
5
6
7
8
9
10
// 全局模式
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
PrepareStmt: true,
})

// 会话模式
tx := db.Session(&Session{PrepareStmt: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)

选择字段

默认情况下,GORM 在查询时会选择所有的字段,可以使用 Select 来指定想要的字段

1
db.Select("Name", "Age").Find(&Users{})

或者定义一个较小的 API 结构体,使用 智能选择字段功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type User struct {
ID uint
Name string
Age int
Gender string
// 假设后面还有几百个字段...
}

type APIUser struct {
ID uint
Name string
}

// 查询时会自动选择 `id`、`name` 字段
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10

总结

ORM 框架的学习以熟悉 API 为主,读一遍官方文档就可以了解个七七八八,剩下的可以用到时再查缺补漏。

参考