基本概念
我们在讨论更新的时候需要明白一点:更新是针对数据库列的操作。使用gorm操作数据库,我们通常会建一个与表结构对应的模型结构体,该模型的每一个字段都对应了数据库表的一列。gorm提供了很多关于更新的操作,比如针对单个字段的db.Update()
,针对多个字段的db.Updates()
具体内容不再赘述,大家自己查阅官方文档
问题引出
翻阅文档我们可以发现,常用的更新操作都是对特定字段用同一个值进行更新,例如对于student
表的score
字段进行更新调用db.Update()
方法时只会把score
列更新为同一个值。如果在某些业务场景下,需要把每一行数据的某个字段更新为不同的值,这个时候最容易想到的办法就是用for循环遍历每一行数据,针对每一行进行不同的更新操作。这种方法虽然能够完成需求,但每执行一次更新都会发起一次对数据库的io操作,这样如果数据量大的话会很慢,有没有一次io就能完成批量更新的方法呢?
解决方案
gorm在创建数据的时候可以用db.Create()
进行批量创建,只需要用一个切片存放需要创建的数据然后create该切片即可,具体的可以参考官方文档。在此方法中只对数据库进行了一次io操作,我们可以基于该方法进行一次io的批量更新操作。
clause语句的OnConflict{}构造器
gorm中的clause语句提供了,对sql子句的构建操作。对于每个操作,GORM 都会创建一个 *gorm.Statement
对象,所有的 GORM API 都是在为 statement
添加、修改 子句
,最后,GORM 会根据这些子句生成 SQL。下面介绍一下网上引用较多的gorm提供的clause.OnConflict{}
子句构造器。
type OnConflict struct {
Columns []Column
Where Where
TargetWhere Where
OnConstraint string
DoNothing bool
DoUpdates Set
UpdateAll bool
}
OnConflict{}
是gorm提供的一个用于处理冲突的构造器,当在插入数据遇到冲突的时候,比如数据已经存在,我们可以通过OnConflict{}
指定一些操作,例如存在则更新。下面介绍一些其中的某些字段
Columns
: 定义重复键冲突时需要检查的列。Where
: 定义检查重复键冲突时的条件。OnConstraint
: 定义检查重复键冲突时使用的约束。DoNothing
: 定义当重复键冲突时不执行任何操作。DoUpdates
: 定义当重复键冲突时执行更新操作,需要传入一个map[string]interface{}
类型的参数,表示需要更新的列及其对应的值。UpdateAll
: 定义当重复键冲突时更新所有列的值,需要传入一个结构体的指针,表示需要更新的记录。
通过OnConflict
的定义我们可以看出,当遇到冲突需要更新的时候我们需要给Columns
填入需要检查冲突的列,给DoUpdates
传入要执行的操作即可。不过,怎么给DoUpdates
赋值呢?
AssignmentColumns函数
clause.AssignmentColumns()
用于在发生冲突时指定要更新的列,我们需要传递一个string
类型的切片,在切片中指定要更新的字段名。假如有一个user表当检测到id发生冲突的时候更新name
和age
列,我们就可以这样操作
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&users)
使用案例
我们用一个案例来给本文进行最后的收尾。假如有一个student
表,表的定义和数据库初始化等操作如下:
//表结构定义
type Student struct {
Name string
Score int
}
func main() {
dsn := "root:123456@tcp(localhost:3306)/chen?charset=utf8&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println("数据库连接失败:", err)
return
}
db.AutoMigrate(&Student{})
var data = []Student{
{ID: 1, Name: "张三", Score: 100},
{ID: 2, Name: "李四", Score: 100},
{ID: 3, Name: "王五", Score: 100},
{ID: 4, Name: "赵六", Score: 100},
}
err = db.Create(&data).Error
if err != nil {
fmt.Println("插入数据出错:", err)
return
}
//查询数据是否插入成功
var res []Student
if err = db.Find(&res).Error; err != nil {
fmt.Println("查询数据出错:", err)
return
}
print(res)
}
//用来打印切片数据的打印函数
func print(datas []Student) {
for _, data := range datas {
fmt.Printf("%+v\n", data)
}
}
控制台输出结果:
chen@DESKTOP-RA4F79H MINGW64 /d/GoProject/src/GormClauseDemo
$ go run main.go
{ID:1 Name:张三 Score:100}
{ID:2 Name:李四 Score:100}
{ID:3 Name:王五 Score:100}
{ID:4 Name:赵六 Score:100}
现在我们修改每个学生的分数都不同的值
//修改切片内的每个分数
for k, _ := range res {
res[k].Score = k
}
//查看修改后的数据
print(res)
//控制台输出
chen@DESKTOP-RA4F79H MINGW64 /d/GoProject/src/GormClauseDemo
$ go run main.go
{ID:1 Name:张三 Score:0}
{ID:2 Name:李四 Score:1}
{ID:3 Name:王五 Score:2}
{ID:4 Name:赵六 Score:3}
这里可以看到切片里的每个学生分数都被修改成了不同的值,
这里有个小细节:我用for range修改切片数据的时候为啥不用直接修改k,v参数的第二个v,而是要通过res[k]的方式修改。因为for range在遍历的时候是把切片复制了一份直接修改value不会影响原有切片,而通过res[k]的方式修改的是切片的底层数组,就算是复制的切片底层数据指向的也是同一个数组,所以用这种方式可以修改原有切片。
执行批量修改:
这里将重要代码放一起
fmt.Println("修改前数据库的数据:")
var res []Student
if err = db.Find(&res).Error; err != nil {
fmt.Println("查询数据出错:", err)
return
}
print(res)
//修改切片内的每个分数
for k, _ := range res {
res[k].Score = k
}
//查看修改后的数据
fmt.Println("期望修改后的数据:")
print(res)
//批量修改
fmt.Println("修改后数据库的数据:")
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{"score"}),
}).Create(&res)
//查询修改后的数据
if err = db.Find(&res).Error; err != nil {
fmt.Println("查询数据出错:", err)
return
}
print(res)
chen@DESKTOP-RA4F79H MINGW64 /d/GoProject/src/GormClauseDemo
$ go run main.go
修改前数据库的数据:
{ID:1 Name:张三 Score:100}
{ID:2 Name:李四 Score:100}
{ID:3 Name:王五 Score:100}
{ID:4 Name:赵六 Score:100}
期望修改后的数据:
{ID:1 Name:张三 Score:0}
{ID:2 Name:李四 Score:1}
{ID:3 Name:王五 Score:2}
{ID:4 Name:赵六 Score:3}
修改后数据库的数据:
{ID:1 Name:张三 Score:0}
{ID:2 Name:李四 Score:1}
{ID:3 Name:王五 Score:2}
{ID:4 Name:赵六 Score:3}
可以看到数据的确被修改了,至此我们所期望的不用for循环进行批量修改的需求完成了。希望本篇文章对大家有所帮助,如果大家有不同的见解欢迎在评论区留言讨论!