gorm 链式操作源码分析

对 gorm 的链式操作一直有疑惑,最近做项目,翻了下源码看了具体的实现,做下记录。

当然推荐先看官方文档了解下链式操作罗:
https://gorm.io/zh_CN/docs/method_chaining.html

看完是不是有很多疑问?例如:

/images/gorm 链式操作分析/why.png
why

getInstance() 源码分析

链式方法在每次调用时,内部会先调用 getInstance() 函数,该函数的实现如下:

 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
func (db *DB) getInstance() *DB {
	if db.clone > 0 {
        // 创建新的 DB 实例,由于 Go 变量零值的原因,新的 DB.clone = 0 
		tx := &DB{Config: db.Config, Error: db.Error}

		if db.clone == 1 {
			// 创建一个全新的 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 {
            // 复用原来的的 statement,即 SQL 语句会复用
			tx.Statement = db.Statement.clone()
			tx.Statement.DB = tx
		}

		return tx
	}

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

再查看 clone 值的使用:

/images/gorm 链式操作分析/clone.png
clone

追进去,发现只有两个函数三处地方对 clone 修改:

 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
func Open(dialector Dialector, opts ...Option) (db *DB, err error) {
    ...
    // 第一个函数,第一处
	db = &DB{Config: config, clone: 1}
    ...
}

func (db *DB) Session(config *Session) *DB {
	var (
		txConfig = *db.Config
		tx       = &DB{
			Config:    &txConfig,
			Statement: db.Statement,
			Error:     db.Error,
            // 第二个函数,第一处
			clone:     1,
		}
	)

    ...
    // 第二个函数,第二处
    // 只有 db.Session(&gorm.config{NewDB:true}) 才不会执行这里的逻辑
    //(因为 NewDB 的零值为 false),调用即 Session 后的新 DB 的 clone 值默认设置为 2
	if !config.NewDB {
		tx.clone = 2
	}
    ...
	return tx
}

其实还有第三个函数会对 clone 值做出修改,即 getInstance() 函数,因为创建一个新的 DB 后的 clone 为零值,默认为 0 。

根据以上源码分析,可以得到下图:

  1. 箭头表示调用 getInstanceclone 的变化;

  2. 椭圆旁边的文字表示 clone 强制改变的方式。

/images/gorm 链式操作分析/clone 状态转变.png
clone 状态转变

注意:

  1. 一旦 clone 为 1 或 2,在首次调用 getInstance() 后,clone 都为变成 0;而 clone 为 0,调用 getInstance() 时会陷入死循环,即无限为 0。

  2. 只有两个函数能修改,将 clone 从 0 变 1 或 2:WithContext()Session()WithContext() 内部调用 Session())。Session() 可以根据传入的 config.NewDB 的值来决定把 clone 变成 1 还是 2。

End

0%