开发过程中,看到同事的代码写了这么一段:
db = db.Session(&gorm.Session{Context: db.Statement.Context}).FirstOrCreate(&entity)
if db.Error !=nil{
return components.ErrorDbInsert.WrapPrintf(db.Error, "Insert error, entity:%s", utils.ToJson(entity))
}
if db.RowsAffected == 0 {
return components.ErrorAlreadyExist
}
我不禁感到疑惑, gorm
的 RowsAffected
在进行查询,如果查到数据,也是有值的,为什么在这里可以用 RowsAffected == 0
来判断数据已存在?
抱着这个疑问,我点开了 FirstOrCreate
的代码:
func (db *DB) FirstOrCreate(dest interface{}, conds ...interface{}) (tx *DB) {
queryTx := db.Limit(1).Order(clause.OrderByColumn{
Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
})
if tx = queryTx.Find(dest, conds...); queryTx.RowsAffected == 0 {
...
return tx.Create(dest)
} else if len(db.Statement.assigns) > 0 {
...
return tx.Model(dest).Updates(assigns)
}
return db
}
我们可以很容易地发现,在 Find
查到数据且 assigns
没有值的情况下, return
的是 db
,而其他情况下 return
的是 tx
。直觉告诉我,原因大概率在这个上面。
Limit、Order、Find
等许多函数都调用了同一个函数 db.getInstance()
:
func (db *DB) getInstance() *DB {
if db.clone > 0 {
tx := &DB{Config: db.Config, Error: db.Error}
if db.clone == 1 { // 吐槽一下这里的魔法值,理解起来真不容易
// clone with new statement
tx.Statement = &Statement{
DB: tx,
ConnPool: db.Statement.ConnPool,
Context: db.Statement.Context,
Clauses: map[string]clause.Clause{},
Vars: make([]interface{}, 0, 8),
}
} else {
// with clone statement
tx.Statement = db.Statement.clone()
tx.Statement.DB = tx
}
return tx
}
return db
}
这个函数很简单,
db.getInstance()
的小结:
看到这里,这个”黑魔法”的原理已经呈现在我们眼前:
在调用 FirstOrCreate
函数的时候,如果此时 clone
不为0,则会在调用 Limit
函数的时候生成一个新的实例 tx
。
因此在查到数据且不进行 Update
的情况下,函数会直接返回 db
,而 RowsAffected
为 0。
基于上述理论,大胆猜想 Session
这个函数必定会改变 db
的 clone
属性,查看 Session
源码后,我如愿找到了:
//Session create new db session
func(db *DB) Session(config *Session) *DB {
var(
txConfig = *db.Config
tx = &DB{
Config: &txConfig,
Statement: db.Statement,
Error: db.Error,
clone: 1, // 设置 clone 的默认值为1
}
)
...
// 回顾 getInstance 函数对 clone = 2 时的处理,NewDB 的含义不言而喻(再次吐槽魔法值)
if !config.NewDB {
tx.clone = 2
}
...
return tx
}
发现在新版本的 gorm
,金柱大佬直接把”魔仙棒”给没收了,不管结果如何,都返回 tx
,因此新版本( v1.23.0
之后)的 gorm
将无法使用这个”黑魔法”:
// FirstOrCreate finds the first matching record, otherwise if not found creates a new instance with given conds.
// Each conds must be a struct or map.
func (db *DB) FirstOrCreate(dest interface{}, conds ...interface{}) (tx *DB) {
tx = db.getInstance()
queryTx := db.Session(&Session{}).Limit(1).Order(clause.OrderByColumn{
Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
})
if result := queryTx.Find(dest, conds...); result.Error == nil {
if result.RowsAffected == 0 {
...
return tx.Create(dest)
} else if len(db.Statement.assigns) > 0 {
...
return tx.Model(dest).Updates(assigns)
}
} else {
tx.Error = result.Error
}
return tx
}
事实上,这个”黑魔法”的使用是不符合 RowsAffected
的原本定义的,开发者把它当成一个bug优化掉也是理所当然,实际上的开发应当尽量不用。
“黑魔法”应当少用,但是值得探究,这可比生啃源码有趣得多。
Original: https://www.cnblogs.com/weirwei/p/16734918.html
Author: weirwei
Title: Gorm 的黑魔法
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/591860/
转载文章受原作者版权保护。转载请注明原作者出处!