精弘网络 2023 暑期后端 Go Web 开发基础课程 —— Gorm(MySQL)
B 站授课链接点击此处

初识

ORM

什么是 ORM

  • ORM 是 Object Relational Mapping 的缩写,译为 ”对象关系映射“,他解决了对象和关系型数据库之间的数据交互问题
  • 程序中的对象/实例关系型数据库映射起来
  • 三个映射关系
    • 数据表对应结构体
    • 数据行对应结构体实例
    • 字段对应结构体字段
  • 以 Go 为例:

为什么使用 ORM

  • 提高开发效率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type UserInfo struct {
    ID   int
    Name string
    Age  int
    Sex  string
}

func main() {
    user := UserInfo{1, "XiMo", 3, "male"}
    // 将 user 存入数据库
    // SQL 语句
    // insert into userinfo values(1,"XiMo",3,"male");

// orm 语句
// orm.Create(&user)
}
  • 缺点
    • 自动生成 SQL 语句,会牺牲一定的性能
    • 对于复杂的数据库操作,ORM 通常难以处理,即使能够处理,也不如直接手写原生 SQL 语句灵活
    • 弱化 SQL 能力

Gorm

  • GORM 是 Go 语言目前比较热门的数据库 ORM 操作库,对开发者也比较友好,使用非常简单

连接

安装

在使用 gorm 之前我们需要下载 gorm 以及对应数据库 mysql 的驱动

1
2
$ go get -u gorm.io/gorm
$ go get -u gorm.io/driver/mysql
简单连接
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
// 定义一个全局变量
var DB *gorm.DB

func Init() {
    user := "root"
    pass := "123456"
    host := "127.0.0.1"
    port := "3306"
    DBname := "gorm_learn"

    // dsn data-source-name 告知数据库所在的位置以及数据库相关的属性
    // user:pass@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
        user, pass, host, port, DBname)

    // 连接数据库,获得 DB 类型实例,用于后面对数据库进行的操作
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database, error = " + err.Error())
    }

    // 赋值给全局变量
    DB = db
}

func main() {
// 做一层简单的封装,后续操作可以直接利用全局变量 DB 进行
    Init()
}

模型定义

模型定义

在使用 ORM 工具时,通常我们需要在代码中定义模型(Model)与数据库中的数据表进行映射,在 GORM 中模型(Model)通常是正常定义的结构体、由 Go 的基本数据类型、实现了  Scanner  和  Valuer  接口的自定义类型及其指针或别名组成
例如:

1
2
3
4
5
6
7
8
9
10
11
type User struct {
    ID           uint
    Name         string
    Email        *string
    Age          uint8
    Birthday     *time.Time
    MemberNumber sql.NullString
    ActivatedAt  sql.NullTime
    CreatedAt    time.Time
    UpdatedAt    time.Time
}

自动迁移建表

AutoMigrate 会根据结构体帮我们自动创建一个表

1
DB.AutoMigrate(&User)

AutoMigrate 的逻辑是只新增,不删除,不修改(可以修改大小)
例如 User 结构体中将 Name 改成 Username,对应的表中会多出一个 username 的字段,但是原本的 name 字段不会删除

Gorm 约定

GORM 倾向于约定优于配置 默认情况下

  • Gorm 使用名为 ID 的字段作为主键
  • 如果没有 TableName 函数,使用结构体的蛇形复数作为表名
  • 字段名的蛇形作为列名
  • 使用 CreatedAtUpdatedAt 字段追踪创建更新时间
1
2
3
4
5
6
7
8
9
// 结构体的蛇形复数作为表名(UserInfo --> user_infos)
// 默认列名是字段名的蛇形小写(CreatedAt --> created_at)
type UserInfo struct {
    ID        uint // 默认为主键
    Name      string
    Age       int
    CreatedAt time.Time // 创建记录时,如果该字段值为零值,则将该字段的值设为当前时间
    UpdatedAt time.Time // 更新记录时,将该字段的值设为当前时间,创建记录时,如果该字段值为零值,则将该字段的值设为当前时间
}

如果遵循 GORM 的约定,就可以少写一些配置、代码。 当然,如果约定不符合你的实际要求,GORM 允许你配置它们

主键(Primary Key)

GORM 默认会使用名为 ID 的字段作为表的主键,你可以通过标签  primaryKey  将其它字段设为主键

1
2
3
4
5
6
7
// 使用 UUID 作为主键
type User struct {
  ID   uint
  UUID int64 `gorm:"primaryKey"`
  Name string
  Age  int64
}

复数表名(Table Name)

表名默认就是结构体的蛇形复数,例如:

1
2
3
4
5
6
7
8
9
type User struct {} // 默认表名是 `users`
type UserInfo struct {} // 默认表名是 `user_infos`

// 将 UserInfo 的表名设置为 `user`
func (UserInfo) TableName() string {
  return "user"
}

// 除了这种方式外还有别的方法,比如临时修改表名、修改 Gorm 的命名策略等,这边不多做涉及,可以自行查阅文档

列名

数据表的列名使用的是 struct 字段名的蛇形命名
可以使用  column  标签来覆盖列名

1
2
3
4
5
type User struct {
    ID       uint   `gorm:"column:user_id"`  // 将列名设为 `user_id`
    UserName string `gorm:"column:name"`     // 将列名设为 `name`
    Age      int64  `gorm:"column:user_age"` // 将列名设为 `user_age`
}

时间戳追踪

CreatedAt

对于有  CreatedAt  字段的模型,创建记录时,如果该字段值为零值,则将该字段的值设为当前时间
可以通过将  autoCreateTime  标签置为  false  来禁用时间戳追踪

1
2
3
type User struct {
    CreatedAt time.Time `gorm:"autoCreateTime:false"`
}
UpdatedAt

对于有  UpdatedAt  字段的模型,更新记录时,将该字段的值设为当前时间。创建记录时,如果该字段值为零值,则将该字段的值设为当前时间
可以通过将  autoUpdateTime  标签置为  false  来禁用时间戳追踪

1
2
3
type User struct {
    UpdatedAt time.Time `gorm:"autoUpdateTime:false"`
}

结构体标记(tag)

声明 model 时,tag 是可选的,GORM 支持以下 tag:tag 名大小写不敏感,但建议使用  camelCase  风格,多个 tag 之间用 ; 分格

标签名 说明
column 指定 db 列名
type 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not nullsizeautoIncrement… 像  varbinary(8)  这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
serializer 指定将数据序列化或反序列化到数据库中的序列化器, 例如: serializer:json/gob/unixtime
size 定义列数据类型的大小或长度,例如  size: 256
primaryKey 将列定义为主键
unique 将列定义为唯一键
default 定义列的默认值
precision 指定列的精度
scale 指定列大小
not null 指定列为 NOT NULL
autoIncrement 指定列为自动增长
autoIncrementIncrement 自动步长,控制连续记录之间的间隔
embedded 嵌套字段
embeddedPrefix 嵌入字段的列名前缀
autoCreateTime 创建时追踪当前时间,对于  int  字段,它会追踪时间戳秒数,您可以使用  nano/milli  来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano
autoUpdateTime 创建/更新时追踪当前时间,对于  int  字段,它会追踪时间戳秒数,您可以使用  nano/milli  来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli
index 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看  索引  获取详情
uniqueIndex 与  index  相同,但创建的是唯一索引
check 创建检查约束,例如  check:age > 13,查看  约束  获取详情
<- 设置字段写入的权限, <-:create  只创建、<-:update  只更新、<-:false  无写入权限、<-  创建和更新权限
-> 设置字段读的权限,->:false  无读权限
- 忽略该字段,-  表示无读写,-:migration  表示无迁移权限,-:all  表示无读写迁移权限
comment 迁移时为字段添加注释

tag 使用如下:

1
2
3
4
5
6
7
type User struct {
    ID       uint   `gorm:"autoIncrement"`                    // 自增主键
    Name     string `gorm:"size:10"`                          // 大小为 10 个字符
    Age      int    `gorm:"size:3; check:age > 0"`            // 大小为 3 位数,并且值要 > 0
    Email    string `gorm:"type:varchar(25); unique;"`        // 类型为 varchar(25),唯一
    Password string `gorm:"type:varchar(20); default:123456"` // 类型为 varchar(20),默认值为 123456
}

gorm.Model

对于一张数据表的每一条数据来说,都有自增主键,创建、更新与删除时间等比较通用的字段,因此 GORM 将其抽出来并定义了 gorm.Model,包含 IDCreatedAtUpdatedAtDeletedAt 四个字段

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"`
}

你可以将它嵌入到你自己的模型中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type User struct {
    gorm.Model
    Name      string
    Age       int
}

// 等效于  
type User struct {  
  ID        uint           `gorm:"primaryKey"`  
  CreatedAt time.Time  
  UpdatedAt time.Time  
  DeletedAt gorm.DeletedAt `gorm:"index"`  
  Name      string  
  Age       int
}

创建记录

模型

1
2
3
4
5
6
type User struct {
    ID   uint
    Name string
    Age  int
    Sex  string
}

创建记录

1
2
3
4
5
6
7
user := User{Name: "XiMo", Age: 3, Sex: "male"}

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

user.ID // 返回插入数据的主键
result.Error // 返回 error
result.RowsAffected // 返回插入记录的条数
  • 注:Create 传入的参数是指针而不是值
  • 由于传递的是一个指针,执行完 Create 函数后 user 中就有了该记录相关的信息,比如创建的 ID

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

1
2
3
4
5
6
7
8
9
users := []User{
        {Name: "惜寞", Age: 3, Sex: "male"},
        {Name: "青鸟", Age: 18, Sex: "male"},
    }

result := db.Create(&users) // 传入切片的指针

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

用指定的字段创建记录

创建记录并为指定的字段分配值:

1
db.Select("Name", "Age").Create(&user)

创建记录并忽略要省略的传递字段的值:

1
db.Omit("Name", "Age").Create(&user)

根据 Map 创建(不常用)

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

// 通过 []map[string]interface{}{} 批量插入
db.Model(&User{}).Create([]map[string]interface{}{
{"Name": "知更鸟", "Age": 19},
{"Name": "桑葚", "Age": 19},
})

更多

查询记录

一般查询

  • GORM 提供了  FirstTakeLast  方法,以便从数据库中检索单个对象
  • 提供了 Find 方法,以便从数据库中检索全部对象
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
var user User
var users []User

// 根据主键查询第一条记录
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;

// 查询指定的某条记录(仅当主键为整型时可用)
db.First(&user, 10)
//// SELECT * FROM users WHERE id = 10;

// 查询所有的记录
db.Find(&users)
//// SELECT * FROM users;

result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // 返回错误
  • 注意:
    • 当目标对象有一个主键值时,将使用主键构建查询条件
    • 使用 First 等方法查询单个记录时,如果查询不到数据会返回 ErrRecordNotFound 错误
    • 使用 Find 查询时,查询不到数据不会返回错误

条件

string 条件

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
// 获取第一条匹配的记录
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// 获取所有匹配的记录
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';

struct & Map 条件

1
2
3
4
5
6
7
8
9
10
11
// 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);

注意:使用 struct 查询时,GORM 将仅使用非零字段进行查询,这意味着如果你的字段的值为 0 '' false 或其他零值时,将不会用于构建查询条件,例如:

1
2
db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
//// SELECT * FROM users WHERE name = "jinzhu";

要在查询条件中包含零值,可以使用 map,它会将所有键值作为查询条件包含在内,例如:

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

指定结构体查询字段

在使用 struct 进行搜索时,指定在查询条件中要使用结构体中的哪些特定值,例如:

1
2
3
4
5
db.Where(&User{Name: "jinzhu"}, "name", "Age").Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;

db.Where(&User{Name: "jinzhu"}, "Age").Find(&users)
// SELECT * FROM users WHERE age = 0;

内联条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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")
// SELECT * FROM users WHERE name = "jinzhu";

db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;

// Struct
db.Find(&users, User{Age: 20})
// SELECT * FROM users WHERE age = 20;

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

Not 条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;

// Not In
db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");

// Struct
db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;

// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;

Or 条件

1
2
3
4
5
6
7
8
9
10
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';

// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);

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

选择特定字段

Select 允许指定要从数据库中检索的字段,没有 Select GORM 将默认选择所有字段

1
2
3
4
5
db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;

db.Select([]string{"name", "age"}).Find(&users)
// SELECT name, age FROM users;

排序

1
2
3
4
5
6
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

// Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

Limit & Offset

Limit 指定要检索的最大记录数,Offset 指定在开始返回记录之前要跳过的记录数

1
2
3
4
5
6
7
8
9
10
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;

// -1 表示检索所有记录
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)

db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;

注意:Offset 不能单独使用,要和 Limit 要一起使用

更多

更新记录

  • 以查询到记录为基础
  • 更新的记录不存在不会报 error

保存所有字段

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, updated_at = '2013-11-17 21:34:10' WHERE id=111;
  • 如果保存值不包含主键(或者主键值在数据库中不存在),它将执行 Create 创建
  • 主键存在则会执行更新保存所有字段
1
2
3
4
5
db.Save(&User{Name: "jinzhu", Age: 100})
// INSERT INTO `users` (`name`,`age`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00")

db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})
// UPDATE `users` SET `name`="jinzhu",`age`=100,`update_at`="0000-00-00 00:00:00" WHERE `id` = 1

更新单个列

  • 需要通过 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's ID is `111`:
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 根据条件和主键更新
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;

更新多列

  • 如果要更新多个字段的话,可以使用 Updates 函数,该函数需要传入一个结构体或 map
  • 在使用结构体时,不会更新零值,如果要更新的话,需要使用 map 或者更新选定字段
1
2
3
4
5
6
7
// 用结构体更新
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;

更新选定字段

如果要在更新时更新所选字段或忽略某些字段,可以使用 selectomit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Select with Map
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;

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

// Select with Struct (select zero value fields)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;

// Select all fields (select all fields include zero value fields)
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

// Select all fields but omit Role (select all fields include zero value fields)
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

批量更新

如果我们没有指定主键值,GORM 将执行批量更新 Model,更新所有符合条件的记录

1
2
3
4
5
6
7
// Update with struct
db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';

// Update with map
db.Model(User{}).Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);

更多

删除记录

  • 以查询到记录为基础
  • 删除的记录不存在不会报 error

删除一条记录

删除一条记录时,删除对象需要指定主键,否则会触发批量删除
:删除的记录不存在不会报 error

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

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

根据主键删除

GORM 允许通过主键和内联条件来删除对象

1
2
3
4
5
6
7
8
9
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);

// 内联条件
db.Delete(&user{}, "name = ?", "jinzhu")
// DELETE FROM users WHERE name = "jinzhu";

批量删除

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

1
2
3
4
5
db.Where("name LIKE ?", "%jinzhu%").Delete(&User{})
// DELETE from users where name LIKE "%jinzhu%";

db.Delete(&User{}, "name LIKE ?", "%jinzhu%")
// DELETE from users where name LIKE "%jinzhu%";

阻止全局删除

当你试图执行不带任何条件的批量删除时,GORM 将不会运行并返回ErrMissingWhereClause  错误
如果一定要这么做,你必须添加一些条件,或者使用原生 SQL,或者开启AllowGlobalUpdate  模式,如下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
db.Delete(&User{}).Error
// gorm.ErrMissingWhereClause

// 需要主键值
db.Delete(&[]User{{Name: "jinzhu1"}, {Name: "jinzhu2"}}).Error
// gorm.ErrMissingWhereClause

db.Where("1 = 1").Delete(&User{})
// DELETE FROM `users` WHERE 1=1

// 原生 SQL
db.Exec("DELETE FROM users")
// DELETE FROM users

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
// DELETE FROM users

软删除

如果你的模型包含了  gorm.DeletedAt 字段(该字段也被包含在 gorm.Model 中),那么该模型将会自动获得软删除的能力
当调用 Delete 时,GORM 并不会从数据库中删除该记录,而是将该记录的 DeleteAt 设置为当前时间,而后的一般查询方法将无法查找到此条记录

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;

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

// 软删除的记录在查询时会被忽略
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

如果你并不想嵌套 gorm.Model,你也可以像下方例子那样开启软删除特性:

1
2
3
4
5
type User struct {
ID int
Deleted gorm.DeletedAt
Name string
}

查找被软删除的记录

你可以使用 Unscoped 来查询到被软删除的记录

1
2
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;

永久删除

你可以使用  Unscoped 来永久删除匹配的记录

1
2
3
// user's ID is `10`
db.Unscoped().Delete(&user)
// DELETE FROM users WHERE id=10;

更多