解读gorm 的链式操作

go的gorm刚用起来比较玄幻。有时会触发多个db操作相互污染的问题。为了弄清这个问题特地研究了一下文档及源码。文档链接地址:https://gorm.io/zh_CN/docs/method_chaining.html
下面我尝试从源码角度理解一下gorm句柄切换的逻辑:

func (db *DB) getInstance() *DB {
    // 句柄之间的转换关键在 db.clone,通过这个标识来确定怎么操作sql(所有操作db的逻辑都存放到这个属性里:db.Statement)
    //clone > 0 时:需要新建一个db.Statement 实例,
    if db.clone > 0 {
       //这里得到的 tx.clone = 0
        tx := &DB{Config: db.Config, Error: db.Error}

        // =1 时代表需要重新定义一个句柄(新的db.Statement实例)(另外提示一下:通过db.Open 获取的句柄 db.clone = 1)
        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 {// > 1时,代表需要从原来的db.Statement clone 一个,(另外提示一下:执行db.Session(&gorm.Session{}) 的时候,db.clone = 2,将用到这里的逻辑)
            // with clone statement
            tx.Statement = db.Statement.clone()
            tx.Statement.DB = tx
        }

        return tx
    }

   // db.clone = 0时,直接返回
    return db
}

可以理解为,clone > 0 时,每次第一次操作数据库后,就会返回一个新的db 句柄,这个新的句柄会保留所有在这个句柄上的操作,直至调用 db.Session(&gorm.Session{})时, 会保留之前的操作,然后会继续链上下次的操作。

在重用db句柄时要注意这个逻辑,特别在事务中,必须重用一个db实例,否则就不是在一个事务中了。这个时候就要小心sql相互污染的问题,比较好的习惯是“一链到底”,如非必要别再次赋值。如下:

//Db() 方法获取gorm.Open() 返回的单例
db := Db().Begin()
//这个db 已经和上一行的db不是一个实例了
db = db.Where("type = 2")
if s, found := getData["status"]; found {
   //这个db 和 第二个 db 是一个实例
   db = db.Where("status = ?", s)
}

优化之后的代码就看起来比较简单了

//这里换一个名字
tr := Db().Begin()
//从tr获取一个db句柄
dbDoSomething1 = tr.Where("type = 2")
if s, found := getData["status"]; found {
   //直接操作db句柄
   dbDoSomething1.Where("status = ?", s)
}
此条目发表在go分类目录,贴了, , , 标签。将固定链接加入收藏夹。