抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

记录Gorm学习手册

声明模型

模型定义

GORM通过将Go结构体(Go Structs)映射到数据库表来简化数据库交互

模型定义

模型是使用普通结构体定义的。这些结构体可以包含具有基本Go类型、指针或这些类型的别名, 甚至是自定义类型(只需要实现database/sql包中的Scanner和Valuer接口)。

考虑以下user模型示例:

1
2
3
4
5
6
7
8
9
10
11
type User struct {
ID uint // 主键
Name string // 一个常规字符串字段
Email *string // 一个指向字符串的指针, 允许空值
Age uint8 // 一个未签名的8位整数
Brithday *time.Time // time.Time指针, 可以为空
MemberNumber sql.NullString // 使用 sql.NullString 处理可为空字符串
ActivatedAt sql.NullTime // Uses sql.NullTime for nullable time fields
CreatedAt time.Time // 创建时间(由GORM自动管理)
UpdatedAt time.Time // 最后一次更新时间(由GORM自动管理)
}

在此模型中:

  • 具体数字型如uintstringuint8直接使用;
  • 指向*string*time.Time类型的指针表示可空字段;
  • 来自database/sql包的sql.NullStringsql.NullTime用于具有更多控制的可空字段;
  • CreateAtUpdateAt是特殊字段, 当记录被创建或更新时, GORM会自动向内填充当前时间;

除了GORM中模型声明的基本特性外, 强调下通过serializer标签支持序列化也很重要; 此功能增强了数据存储和检索的灵活性, 特别是需要自定义序列化逻辑的字段;

约定

  1. 主键: GORM使用一个名为ID的字段作为每个模型的默认主键;
  2. 表名: 默认情况下, GORM将结构体名称转化为snake_case并为表名加上复数形式; 例如, 一个User结构体在数据库中表名变成users;
  3. 列名: GORM自动将结构体字段名转换为snake_case作为数据库中列名;
  4. 时间戳字段: GORM使用字段CreatedAtUpdatedAt来自动跟踪记录的创建和更新时间;

gorm.Model

GORM提供了一个预定义的结构体, 名为gorm.Model, 其中包含常用字段:

1
2
3
4
5
6
7
// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
  • 将其嵌入在结构体中: 可以直接在结构体中嵌入gorm.Model, 以便自动包含这些字段;
  • 包含的字段:
    • ID: 每个记录的唯一标识符(主键);
    • CreatedAt: 在创建时自动设置为当前时间;
    • UpdatedAt: 每当记录更新时, 自动更新为当前时间;
    • DeletedAt: 用于软删除;

CRUD接口

创建

创建记录

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 // 返回插入记录的条数

我们还可以使用Create()创建多项记录:

1
2
3
4
5
6
7
8
9
users := []*User{
{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
{Name: "Jackson", Age: 19, Birthday: time.Now()},
}

result := db.Create(&users)

result.Error // 返回错误
result.RowsAffected // 返回插入记录的条数

无法向create传递结构体, 所以应该传入数据的指针

用指定的字段创建记录

创建记录并为指定字段赋值

1
2
db.Select("Name", "Age", "CreateAt").Create(&user)
// INSERT INTO `users` (`name`, `age`, `create_at`) VALUES ("Jinzhu", 18, "2024-07-04 11:05:21.775")

创建记录并忽略传递给Omit的字段值

1
2
db.Omit("Name", "Age", "CreateAt").Create(&user)
// INSERT INTO `users` (`birthday`, `updated_at`) VALUES ("2024-01-01 00:00:00.000", "2024-07-04 11:05:21.775")

批量插入

要高效地插入大量记录, 请将切片传递给Create方法。GORM将生成一条SQL来插入所有数据, 以返回所有主键值, 并触发Hook方法, 当这些记录可以被分割成多个批次时, GORM会开启一个事务来处理它们。

1
2
3
4
5
6
7
8
9
10
var users = []User{
{Name: "jinzhu1"},
{Name: "jinzhu2"},
{Name: "jinzhu3"},
}
db.Create(&users)

for _, user := range users {
user.ID // 1,2,3
}

可以通过db.CreateInBatches方法来指定批量插入的批次大小

1
2
3
4
5
6
7
8
var users []User{
{Name: "jinzhi1"},
...
{Name}: "jinzhi1000",
}

// 插入批次大小为100
db.CreateInBatches(&users, 100)

注意: 使用CreateBatchSize选项初始化GORM实例后, 此后进行创建&关联操作时所有的INSERT行为都会遵循初始化的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
CreateBatchSize: 1000,
})

if err != nil {...}

db := db.Session(&gorm.Session{CreateBatchSize: 1000})

users := [5000]User{{Name: "jinzhu", Pets: []Pet{pet1, pet2, pet3}}}

db.Create(&users)
// INSERT INTO users xxx (5 batches)
// INSERT INTO pets xxx (15 batches)

创建钩子

GORM允许用户通过实现这些接口BeforeSaveBeforeCreateAfterSaveAfterCreate来自定义钩子。这些钩子方法会在创建一条记录时被调用;

1
2
3
4
5
6
7
8
func (u *User) BeforeCreate (tx *gorm.DB) (err error) {
u.UUID = uuid.New()

if u.Role == "admin" {
return errors.New("invalid code")
}
return
}

如果想跳过Hooks方法, 可以使用SkipHooks会话模式, 例子如下:

1
2
3
DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)
DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)
DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(&user, 100)

根据 Map 创建

GORM支持通过map[string]interface{}[]map[string]interface{}{}来创建记录。

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 },
})

注意: 当使用map来创建时, 钩子方法不会执行, 关联不会被保存且不会回写主键。

评论