当前位置:网站首页>gin框架学习-GORM框架进阶之CRUD接口(数据库增删改查操作)
gin框架学习-GORM框架进阶之CRUD接口(数据库增删改查操作)
2022-07-31 05:10:00 【lin钟一】
前言
感谢开源项目gin-vue-admin,以及1010工作室的教程
本人学识尚浅,代码的注释基于源码的注释翻译,如有错误,请评论指出,谢谢!
详细可见个人博客:https://linzyblog.netlify.app/
一、创建
GORM里的创建(Create方法),也就是数据库插入语句(Insert语句),可以创建单条或者多条,指定字段创建等
1、Create方法
1)创建单条记录
user := User{
Name: "linzy", Age: 23, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
user.ID // 返回插入数据的主键
result.Error // 返回 error
result.RowsAffected // 返回插入记录的条数
2)创建多条记录
要有效地插入大量记录,需要将一个 slice 切片传递给 Create 方法。 将切片数据传递给 Create 方法,GORM 将生成一个单一的 SQL 语句来插入所有数据,并回填主键的值,钩子方法也会被调用。
var users = []User{
{
Name: "linzy1"}, {
Name: "linzy2"}, {
Name: "linzy3"}}
db.Create(&users)
for _, user := range users {
user.ID // 1,2,3
}
3)指定字段创建记录
- 用 Select 方法指定需要创建的字段
db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("linzy", 23, "2022-07-08 11:05:21.775")
- 用 Omit 方法会更新未给出的字段创建记录。
db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2022-07-04 00:00:00.000", "2022-07-08 11:05:21.775")
2、CreateInBatches
使用 CreateInBatches 创建时,你还可以指定创建的数量,例如:
var 用户 = []User{
name: "linzy_1"}, ...., {
Name: "linzy_10000"}}
// 数量为 100
db.CreateInBatches(用户, 100)
Upsert 和 Create With Associations 也支持批量插入
3、创建钩子
GORM 允许用户定义的钩子有 BeforeSave, BeforeCreate, AfterSave, AfterCreate 创建记录时将调用这些钩子方法
例如:
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()
if u.Role == "admin" {
return errors.New("invalid role")
}
return
}
如果您想跳过 钩子 方法,您可以使用 SkipHooks 会话模式,例如:
//SkipHooks为true表示当前方法 不执行钩子方法
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)
4、根据 Map 创建
通常我们用GORM都是用结构体 struct 来创建记录,而GORM 也支持根据 map[string]interface{} 和 []map[string]interface{}{} 创建记录,例如:
//创建单条记录
db.Model(&User{
}).Create(map[string]interface{
}{
"Name": "linzy", "Age": 23,
})
//创建多条记录
db.Model(&User{
}).Create([]map[string]interface{
}{
{
"Name": "linzy_1", "Age": 23},
{
"Name": "linzy_2", "Age": 66},
{
"Name": "linzy_3", "Age": 88},
})
注意:根据 map 创建记录时,association (查找关联方法)不会被调用,且主键也不会自动填充
5、高级选项
1)关联创建
创建关联数据时,如果关联值是非零值,这些关联会被 upsert,且它们的 Hook 钩子方法也会被调用
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
type User struct {
gorm.Model
Name string
CreditCard CreditCard // 一对一的关系
}
db.Create(&User{
Name: "linzy",
CreditCard: CreditCard{
Number: "123456789"},
})
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...
您也可以通过 Select、 Omit 跳过关联保存,例如:
db.Omit("CreditCard").Create(&user)
// 跳过所有关联
db.Omit(clause.Associations).Create(&user)
2)默认值
您可以通过标签 default 为字段定义默认值,如:
插入记录到数据库时,默认值 会被用于 填充值为 零值 的字段
type User struct {
ID int64
Name string `gorm:"default:linzy"`
Age int64 `gorm:"default:23"`
}
注意: 像 0、‘’、false 等零值,不会将这些字段定义的默认值保存到数据库。您需要使用指针类型或 Scanner/Valuer 来避免这个问题,例如:
type User struct {
gorm.Model
Name string
Age *int `gorm:"default:23"`
Active sql.NullBool `gorm:"default:true"`
}
注意: 若要数据库有默认、虚拟 / 生成的值,你必须为字段设置 default 标签。若要在迁移时跳过默认值定义,你可以使用 default:(-),例如:
type User struct {
ID string `gorm:"default:uuid_generate_v3()"` // 数据库函数
FirstName string
LastName string
Age uint8
FullName string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);`
}
二、查询
GORM里的查询(Find方法),也就是数据库查询语句(Select语句),可以查询所有数据、查询指定条件数据,查询首条数据,查询最后一条数据
1、查询单条数据
GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误
// 获取第一条记录(主键升序)
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
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
- First、Last 方法会根据主键查找到第一个、最后一个记录, 它仅在通过结构体 struct 或提供 model 值进行查询时才起作用。 如果 model 类型没有定义主键,则按第一个字段排序,例如:
var user User
// 可以
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// 可以
result := map[string]interface{
}{
}
db.Model(&User{
}).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// 不行
result := map[string]interface{
}{
}
db.Table("users").First(&result)
// 但可以配合 Take 使用
result := map[string]interface{
}{
}
db.Table("users").Take(&result)
// 根据第一个字段排序
type Language struct {
Code string
Name string
}
db.First(&Language{
})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
2、根据主键查询
您可以使用 内联条件 来检索对象。 传入字符串参数时注意避免 SQL 注入问题
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
db.Find(&users, []int{
1, 2, 3})
// SELECT * FROM users WHERE id IN (1,2,3);
3、查询全部数据
// 获取全部记录
result := db.Find(&users)
// SELECT * FROM users;
result.RowsAffected // 返回找到的记录数,相当于 `len(users)`
result.Error // returns error
4、条件查询
条件查询即查询满足条件的所有数据,GORM框架给用户提供了 String条件 、Struct & Map 条件、内联条件 、Not 条件以及 Or 条件查询方式
1)String条件
通过Where方法,对String里的?进行填充,来完成条件查询
// 获取第一条匹配的记录
db.Where("name = ?", "linzy").First(&user)
// SELECT * FROM users WHERE name = 'linzy' ORDER BY id LIMIT 1;
// 获取全部匹配的记录
db.Where("name <> ?", "linzy").Find(&users)
// SELECT * FROM users WHERE name <> 'linzy';
// IN
db.Where("name IN ?", []string{
"linzy", "linzy2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('linzy','linzy2');
// LIKE
db.Where("name LIKE ?", "%in%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%in%';
// AND
db.Where("name = ? AND age >= ?", "linzy", "22").Find(&users)
// SELECT * FROM users WHERE name = 'linzy' 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';
2)Struct & Map 条件
// Struct
db.Where(&User{
Name: "linzy", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "linzy" AND age = 20 ORDER BY id LIMIT 1;
// Map
db.Where(map[string]interface{
}{
"name": "linzy", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "linzy" AND age = 20;
// 主键切片条件
db.Where([]int64{
20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
注意: 当使用结构作为条件查询时,GORM 只会查询非零值字段。这意味着如果您的字段值为 0、‘’、false 或其他 零值,该字段不会被用于构建查询条件,例如:
db.Where(&User{
Name: "linzy", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "linzy";
您可以使用 map 来构建查询条件,例如:
db.Where(map[string]interface{
}{
"Name": "linzy", "Age": 0}).Find(&users)
// SELECT * FROM users WHERE name = "linzy" AND age = 0;
3)内联条件
用法跟 Where 方法一样
// SELECT * FROM users WHERE id = 23;
// 根据主键获取记录,如果是非整型主键
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';
// Plain SQL
db.Find(&user, "name = ?", "linzy")
// SELECT * FROM users WHERE name = "linzy";
db.Find(&users, "name <> ? AND age > ?", "linzy", 20)
// SELECT * FROM users WHERE name <> "linzy" 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;
4)Not 条件
Not在sql语句中是一个逻辑运算符,取反的用处,真为假,假为真,语句用法跟 Where 方法一样
db.Not("name = ?", "linzy").First(&user)
// SELECT * FROM users WHERE NOT name = "linzy" ORDER BY id LIMIT 1;
// Not In
db.Not(map[string]interface{
}{
"name": []string{
"linzy", "linzy 2"}}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("linzy", "linzy 2");
// Struct
db.Not(User{
Name: "linzy", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "linzy" AND age <> 18 ORDER BY id LIMIT 1;
// 不在主键切片中的记录
db.Not([]int64{
1, 2, 3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
5)Or条件
在 Where 方法和 内联条件 存在多个条件的时候都是用AND联系,表示条件都必须满足的数据,那我们如果只需要满足其中一种条件呢,那就需要 Or条件 了,语句用法跟 Where 方法一样
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
// Struct
db.Where("name = 'linzy'").Or(User{
Name: "linzy 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'linzy' OR (name = 'linzy 2' AND age = 18);
// Map
db.Where("name = 'linzy'").Or(map[string]interface{
}{
"name": "linzy 2", "age": 18}).Find(&users)
// SELECT * FROM users WHERE name = 'linzy' OR (name = 'linzy 2' AND age = 18);
5、选择特定字段
选择您想从数据库中检索的字段,默认情况下会选择全部字段
db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;
db.Select([]string{
"name", "age"}).Find(&users)
// SELECT name, age FROM users;
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;
6、Order排序
指定从数据库检索记录时的排序方式
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
// 多个 order
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
db.Clauses(clause.OrderBy{
Expression: clause.Expr{
SQL: "FIELD(id,?)", Vars: []interface{
}{
[]int{
1, 2, 3}}, WithoutParentheses: true},
}).Find(&User{
})
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)
7、Limit & Offset
- Limit 指定获取记录的最大数量
- Offset 指定在开始返回记录之前要跳过的记录数量
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;
// 通过 -1 消除 Limit 条件
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)
db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;
db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;
// 通过 -1 消除 Offset 条件
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)
8、Group & Having
- Group 指定字段进行分组
- Having 指定字段分组后的条件查询
type result struct {
Date time.Time
Total int
}
db.Model(&User{
}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name`
db.Model(&User{
}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
for rows.Next() {
...
}
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
for rows.Next() {
...
}
type Result struct {
Date time.Time
Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
9、Distinct去重
从模型中选择不相同的值,简而言之,把选定的字段里重复数据去掉,只留下一条
Distinct 也可以配合 Pluck, Count 使用
db.Distinct("name", "age").Order("name, age desc").Find(&results)
10、Joins连接
数据空的连接操作,有左连接(left join),右连接(right join),全连接(outer join)、内连接(inner join),指定 Joins 条件
type result struct {
Name string
Email string
}
func main() {
//左连接
db.Model(&User{
}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{
})
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
...
}
db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)
// 带参数的多表连接
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "[email protected]").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
}
11、Joins 预加载
您可以使用 Joins 实现单条 SQL 预加载关联记录,例如:
db.Joins("Company").Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;
12、Scan
Scan 结果存储于结构体 struct,用法与 Find 类似
type Result struct {
Name string
Age int
}
func main() {
var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)
// 原生 SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)
}
三、更新
GORM里的更新(Update方法),也就是数据库更新语句(Update语句),可以更新单列、更新多列、更新选定列
1、更新单列
当使用 Update 更新单个列时,你需要指定条件,否则会返回 ErrMissingWhereClause 错误。当使用了 Model 方法,且该对象主键有值,该值会被用于构建条件,例如:
// 条件更新
db.Model(&User{
}).Where("active = ?", true).Update("name", "linzy")
// UPDATE users SET name='linzy', updated_at='2013-11-17 21:34:10' WHERE active=true;
// User 的 ID 是 `111`
db.Model(&user).Update("name", "linzy")
// UPDATE users SET name='linzy', updated_at='2013-11-17 21:34:10' WHERE id=111;
// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "linzy")
// UPDATE users SET name='linzy', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
2、更新多列
Updates 方法支持 struct 和 map[string]interface{} 参数。当使用 struct 更新时,默认情况下,GORM 只会更新非零值的字段
// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{
Name: "linzy", Age: 18, Active: false})
// UPDATE users SET name='linzy', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{
}{
"name": "linzy", "age": 18, "actived": false})
// UPDATE users SET name='linzy', age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
注意:当通过 struct 更新时,GORM 只会更新非零字段。 如果您想确保指定字段被更新,你应该使用 Select 更新选定字段,或使用 map 来完成更新操作
3、更新选定字段
如果您想要在更新时选定、忽略某些字段,您可以使用 Select、Omit
// Select 和 Map
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{
}{
"name": "linzy", "age": 18, "actived": false})
// UPDATE users SET name='linzy' WHERE id=111;
db.Model(&user).Omit("name").Updates(map[string]interface{
}{
"name": "linzy", "age": 18, "actived": false})
// UPDATE users SET age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
// Select 和 Struct (可以选中更新零值字段)
db.Model(&result).Select("Name", "Age").Updates(User{
Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
4、保存所有字段
Save 会保存所有的字段,即使字段是零值
db.First(&user)
user.Name = "linzy 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='linzy 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
5、更新 Hook
对于更新操作,GORM 支持 BeforeSave、BeforeUpdate、AfterSave、AfterUpdate 钩子,这些方法将在更新记录时被调用
例如:
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to update")
}
return
}
6、批量更新
如果您尚未通过 Model 指定记录的主键,则 GORM 会执行批量更新
// 根据 struct 更新
db.Model(User{
}).Where("role = ?", "admin").Updates(User{
Name: "linzy", Age: 18})
// UPDATE users SET name='linzy', age=18 WHERE role = 'admin;
// 根据 map 更新
db.Table("users").Where("id IN ?", []int{
10, 11}).Updates(map[string]interface{
}{
"name": "linzy", "age": 18})
// UPDATE users SET name='linzy', age=18 WHERE id IN (10, 11);
1)阻止全局更新
如果在没有任何条件的情况下执行批量更新,默认情况下,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误
对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:
db.Model(&User{
}).Update("name", "linzy").Error // gorm.ErrMissingWhereClause
db.Model(&User{
}).Where("1 = 1").Update("name", "linzy")
// UPDATE users SET `name` = "linzy" WHERE 1=1
db.Exec("UPDATE users SET name = ?", "linzy")
// UPDATE users SET name = "linzy"
db.Session(&gorm.Session{
AllowGlobalUpdate: true}).Model(&User{
}).Update("name", "linzy")
// UPDATE users SET `name` = "linzy"
2)更新的记录数
获取受更新影响的行数
// 通过 `RowsAffected` 得到更新的记录数
result := db.Model(User{
}).Where("role = ?", "admin").Updates(User{
Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin;
result.RowsAffected // 更新的记录数
result.Error // 更新的错误
7、高级选项
1)使用 SQL 表达式更新
GORM 允许使用 SQL 表达式更新列,例如:
// product 的 ID 是 `3`
db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
db.Model(&product).Updates(map[string]interface{
}{
"price": gorm.Expr("price * ? + ?", 2, 100)})
// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
db.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3;
db.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3 AND quantity > 1;
2)根据子查询进行更新
使用子查询更新表
db.Model(&user).Update("company_name", db.Model(&Company{
}).Select("name").Where("companies.id = users.company_id"))
// UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);
db.Table("users as u").Where("name = ?", "linzy").Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
db.Table("users as u").Where("name = ?", "linzy").Updates(map[string]interface{
}{
}{
"company_name": db.Table("companies as c").Select("name").Where("c.id = u.company_id")})
3)不使用 Hook 和时间追踪
如果您想在更新时跳过 Hook 方法且不追踪更新时间,可以使用 UpdateColumn、UpdateColumns,其用法类似于 Update、Updates
// 更新单个列
db.Model(&user).UpdateColumn("name", "hello")
// UPDATE users SET name='hello' WHERE id = 111;
// 更新多个列
db.Model(&user).UpdateColumns(User{
Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE id = 111;
// 更新选中的列
db.Model(&user).Select("name", "age").UpdateColumns(User{
Name: "hello", Age: 0})
// UPDATE users SET name='hello', age=0 WHERE id = 111;
四、删除
GORM里的更新(Delete方法),也就是数据库删除语句(Delete语句),可以删除单条记录,删除多条记录,根据主键删除,删除Hook钩子
1、删除单条记录
删除一条记录时,删除对象需要指定主键,否则会触发 批量 Delete
// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;
// 带额外条件的删除
db.Where("name = ?", "linzy").Delete(&email)
// DELETE from emails where id = 10 AND name = "linzy";
2、删除多条记录
如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录
db.Where("email LIKE ?", "%linzy%").Delete(Email{
})
// DELETE from emails where email LIKE "%linzy%";
db.Delete(Email{
}, "email LIKE ?", "%linzy%")
//内联 DELETE from emails where email LIKE "%linzy%";
1)阻止全局删除
如果在没有任何条件的情况下执行批量删除,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误
对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:
db.Delete(&User{
}).Error // gorm.ErrMissingWhereClause
db.Where("1 = 1").Delete(&User{
})
// DELETE FROM `users` WHERE 1=1
db.Exec("DELETE FROM users")
// DELETE FROM users
db.Session(&gorm.Session{
AllowGlobalUpdate: true}).Delete(&User{
})
// DELETE FROM users
3、根据主键删除
GORM 允许通过内联条件指定主键来检索对象,但只支持整型数值,因为 string 可能导致 SQL 注入。
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);
4、删除Hook钩子
对于删除操作,GORM 支持 BeforeDelete、AfterDelete Hook,在删除记录时会调用这些方法
func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to delete")
}
return
}
5、*软删除
如果您的模型包含了一个 gorm.deletedat 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力!
拥有软删除能力的模型调用 Delete 时,记录不会被从数据库中真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过正常的查询方法找到该记录。
// user 的 ID 是 `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,您也可以这样启用软删除特性:
type User struct {
ID int
Deleted gorm.DeletedAt
Name string
}
1)查找被软删除的记录
您可以使用 Unscoped 找到被软删除的记录
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
2)永久删除
您也可以使用 Unscoped 永久删除匹配的记录
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;
五、SQL 构建器
1、原生 SQL
- 原生查询 SQL 和 Scan
type Result struct {
ID int
Name string
Age int
}
func main() {
var result Result
db.Raw("SELECT id, name, age FROM users WHERE id = ?", 3).Scan(&result)
var age int
db.Raw("select sum(age) from users where role = ?", "admin").Scan(&age)
}
- Exec 原生 SQL
db.Exec("DROP TABLE users")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN ?", time.Now(), []int64{
1, 2, 3})
// Exec SQL 表达式
db.Exec("update users set money=? where name = ?", gorm.Expr("money * ? + ?", 10000, 1), "linzy")
注意 GORM 允许缓存预编译 SQL 语句来提高性能
2、命名参数
GORM 支持 sql.NamedArg、map[string]interface{}{} 或 struct 形式的命名参数,例如:
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "linzy")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "linzy" OR name2 = "linzy"
db.Where("name1 = @name OR name2 = @name", map[string]interface{
}{
"name": "linzy2"}).First(&result3)
// SELECT * FROM `users` WHERE name1 = "linzy2" OR name2 = "linzy2" ORDER BY `users`.`id` LIMIT 1
// 原生 SQL 及命名参数
db.Raw("SELECT * FROM users WHERE name1 = @name OR name2 = @name2 OR name3 = @name",
sql.Named("name", "linzy1"), sql.Named("name2", "linzy2")).Find(&user)
// SELECT * FROM users WHERE name1 = "linzy1" OR name2 = "linzy2" OR name3 = "linzy1"
db.Exec("UPDATE users SET name1 = @name, name2 = @name2, name3 = @name",
sql.Named("name", "linzynew"), sql.Named("name2", "linzynew2"))
// UPDATE users SET name1 = "linzynew", name2 = "linzynew2", name3 = "linzynew"
db.Raw("SELECT * FROM users WHERE (name1 = @name AND name3 = @name) AND name2 = @name2",
map[string]interface{
}{
"name": "linzy", "name2": "linzy2"}).Find(&user)
// SELECT * FROM users WHERE (name1 = "linzy" AND name3 = "linzy") AND name2 = "linzy2"
type NamedArgument struct {
Name string
Name2 string
}
db.Raw("SELECT * FROM users WHERE (name1 = @Name AND name3 = @Name) AND name2 = @Name2",
NamedArgument{
Name: "linzy", Name2: "linzy2"}).Find(&user)
// SELECT * FROM users WHERE (name1 = "linzy" AND name3 = "linzy") AND name2 = "linzy2"
3、DryRun 模式
在不执行的情况下生成 SQL ,可以用于准备或测试生成的 SQL
stmt := db.Session(&Session{
DryRun: true}).First(&user, 1).Statement
stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = $1 ORDER BY `id`
stmt.Vars //=> []interface{}{1}
- Row & Rows
获取 *sql.Row 结果
// 使用 GORM API 构建 SQL
row := db.Table("users").Where("name = ?", "linzy").Select("name", "age").Row()
row.Scan(&name, &age)
// 使用原生 SQL
row := db.Raw("select name, age, email from users where name = ?", "linzy").Row()
row.Scan(&name, &age, &email)
- 获取 *sql.Rows 结果
// 使用 GORM API 构建 SQL
rows, err := db.Model(&User{
}).Where("name = ?", "linzy").Select("name, age, email").Rows()
defer rows.Close()
for rows.Next() {
rows.Scan(&name, &age, &email)
// 业务逻辑...
}
// 原生 SQL
rows, err := db.Raw("select name, age, email from users where name = ?", "linzy").Rows()
defer rows.Close()
for rows.Next() {
rows.Scan(&name, &age, &email)
// 业务逻辑...
}
转到 FindInBatches 获取如何在批量中查询和处理记录的信息, 转到 Group 条件 获取如何构建复杂 SQL 查询的信息
4、将 sql.Rows 扫描至 model
使用 ScanRows 将一行记录扫描至 struct,例如:
rows, err := db.Model(&User{
}).Where("name = ?", "linzy").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
var user User
for rows.Next() {
// ScanRows 将一行扫描至 user
db.ScanRows(rows, &user)
// 业务逻辑...
}
参考GORM中文文档:https://learnku.com/docs/gorm/v2/connecting_to_the_database/9731#64cea3
边栏推荐
猜你喜欢
剑指offer基础版 ----- 第28天
Redis进阶 - 缓存问题:一致性、穿击、穿透、雪崩、污染等.
Anaconda configure environment directives
Kubernetes 证书可用年限修改
第7章 网络层第2次练习题答案(第三版)
[Introduction to MySQL 8 to Mastery] Basics - silent installation of MySQL on Linux system, cross-version upgrade
剑指offer专项突击版 ---- 第1天
If the account number or password is entered incorrectly for many times, the account will be banned.
The interviewer asked me TCP three handshake and four wave, I really
剑指offer专项突击版 --- 第 4 天
随机推荐
剑指offer基础版 ---- 第27天
数据库上机实验4 数据更新和视图
对list集合进行分页,并将数据显示在页面中
C语言实验五 循环结构程序设计(二)
账号或密码多次输入错误,进行账号封禁
uni-app进阶之内嵌应用【day14】
Goodbye to the cumbersome Excel, mastering data analysis and processing technology depends on it
初涉C语言
16 【打包上线 图片懒加载】
数据库上机实验6 数据库完整性
【C语言趣味小游戏——猜数字】
13 【代理配置 插槽】
With MVC, why DDD?
Element concatenation operations in numpy and pytorch: stack, concatenat, cat
C语言实验四 循环结构程序设计(一)
tf.keras.utils.pad_sequences()
Anaconda配置环境指令
剑指offer专项突击版 --- 第 4 天
第7章 网络层第3次练习题答案(第三版)
梳理一下自己常用的快捷键