惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
GbyAI
GbyAI
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 叶小钗
U
Unit 42
Engineering at Meta
Engineering at Meta
aimingoo的专栏
aimingoo的专栏
Y
Y Combinator Blog
Google DeepMind News
Google DeepMind News
Vercel News
Vercel News
美团技术团队
雷峰网
雷峰网
Recent Announcements
Recent Announcements
有赞技术团队
有赞技术团队
D
DataBreaches.Net
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Apple Machine Learning Research
Apple Machine Learning Research
J
Java Code Geeks
罗磊的独立博客
MyScale Blog
MyScale Blog
博客园_首页
IT之家
IT之家
F
Fortinet All Blogs
博客园 - Franky

Mohuishou

如何实现支持多集群的 Kubernetes Operator? 第三方应用如何调用我们 kubebuilder 生成的自定义资源? Kubernetes 简明教程 k8s job 为何迟迟不能结束? Go 工程化(十一) 如何优雅的写出 repo 层代码 Go 工程化(十) 如何在整洁架构中使用事务? 给博客添加章节目录 使用 Notion Database 管理静态博客文章 一个普通 Go 开发的三年 4. localhost 就一定是 localhost 么? Go可用性(七) 总结: 一张图串联可用性知识点 Go可用性(六) 熔断 10. 总结 9. kubebuilder 进阶: 源码分析 8. kubebuilder 进阶: webhook 7. kubebuilder 进阶: 测试 6. kubebuilder 实战: status & event 5. kubebuilder 实战: CRUD 4. kustomize 简明教程 3. KubeBuilder 简明教程 2. Kind: 如何快速搭建本地 K8s 开发环境? 1. Operator概述: 如何对 Kubernetes 进行扩展 Go可用性(五) 自适应限流 Go可用性(四) 漏桶算法 Go可用性(三) 令牌桶的实现 rate/limt Go可用性(二) 令牌桶原理及使用 Go可用性(一) 隔离设计 Go并发编程(十二) Singleflight Go工程化(九) 项目重构实践 Go工程化(八) 单元测试
GORM避坑指南之含关联关系的更新
mohuishou <1@lailin.xyz> · 2019-01-29 · via Mohuishou

注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用

在 GORM 的文档当中有说明,使用Update, Updates时只会更新改变的字段,但是出现关联关系的时候情况似乎有了一些微妙的变化

If you only want to update changed Fields, you could use Update, Updates

先说结论

db.Model(&user).Update("name", "hello")如果user包含关联关系,user的关联关系将被自动更新

避坑

1.如果确认不会使用到关联相关的回调,可以直接使用UpdateColumn,UpdateColumns方法

下面是来自官方文档的例子:

1
2
3
4
5
6
7
// Update single attribute, similar with `Update`
db.Model(&user).UpdateColumn("name", "hello")
//// UPDATE users SET name='hello' WHERE id = 111;

// Update multiple attributes, similar with `Updates`
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18 WHERE id = 111;

2.如果需要用到相关的回调,可以手动指定Model里面的结构体

1
db.Model(&User{Model: Model{ID: 1}}).UpdateColumn("name", "hello")

复现

下面这一段是官方文档中的例子,只会更新更新users表的name字段

1
2
3
// Update single attribute if it is changed
db.Model(&user).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

但是如果Model(&struct),struct包含关联关系时,struct关联关系将被更新,如以下所示:

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
30
31
32
33
34
type Product struct {
gorm.Model
Code string
Price uint
Applications []Application
}

type Application struct {
gorm.Model
Name string
ProductID uint
}

func main() {
// Migrate the schema
db.AutoMigrate(&Product{}, &Application{})

// Create
db.Create(&Product{Code: "L1212", Price: 1000})

// Read
var product Product
db.First(&product, 1) // find product with id 1
product.Applications = []Application{
{
Name: "test",
},
}
// Update - update product's price to 2000
db.Model(&product).Update("Price", 1000)
// UPDATE products SET price = '1000', updated_at = '2019-01-29 21:58:52' WHERE products.deleted_at IS NULL
// INSERT INTO applications (created_at,updated_at,deleted_at,name,product_id) VALUES ('2019-01-29 21:58:52','2019-01-29 21:58:52',NULL,'test','0')
}

溯源

该部分默认对 GORM 的源码有一定的了解

查看Model相关的源码,我们可以发现Model其实就是clone了一个DB对象然后将传入的指针赋值给Value

1
2
3
4
5
6
7
8
9
10
// Model specify the model you would like to run db operations
// // update all users's name to `hello`
// db.Model(&User{}).Update("name", "hello")
// // if user's primary key is non-blank, will use it as condition, then will only update the user's name to `hello`
// db.Model(&user).Update("name", "hello")
func (s *DB) Model(value interface{}) *DB {
c := s.clone()
c.Value = value
return c
}

查看Update/Updates相关的源码我们可以发现,这里讲需要更新的字段通过InstanceSet("gorm:update_interface", values)保存了下来

1
2
3
4
5
6
7
8
9
10
11
12
// Update update attributes with callbacks, refer: https://jinzhu.github.io/gorm/crud.html#update
func (s *DB) Update(attrs ...interface{}) *DB {
return s.Updates(toSearchableMap(attrs...), true)
}

// Updates update attributes with callbacks, refer: https://jinzhu.github.io/gorm/crud.html#update
func (s *DB) Updates(values interface{}, ignoreProtectedAttrs ...bool) *DB {
return s.NewScope(s.Value).
Set("gorm:ignore_protected_attrs", len(ignoreProtectedAttrs) > 0).
InstanceSet("gorm:update_interface", values).
callCallbacks(s.parent.callbacks.updates).db
}

再看看关联关系更新的代码, 只有一个参数scope,scope哪儿来的呢,上面的s.NewScope(s.Value),这个地方其实也是将最开始Model中的 value拷贝了一份

1
2
3
4
5
6
7
8
func saveAfterAssociationsCallback(scope *Scope) {
// 判断是否存在关系关系然后更新bala bala...
for _, field := range scope.Fields() {
autoUpdate, autoCreate, saveReference, relationship := saveAssociationCheck(scope, field)
//...
}
//...
}

看到这个了差不多就可明白了,主要原因是因为Modelvalue一直跟随到了最后,导致最后执行关联关系更新回调的时候,检测到有关联数据数据表中不存在,就会自然的根据关联关系插入进去

相关资源

  1. 目前还不清楚这是一个 bug 还是一个 feature,先提交了一个 issue: https://github.com/jinzhu/gorm/issues/2278
  2. 一个十分边缘的 gorm 的 bug

关注我获取更新

猜你喜欢