我们可以先看 Scan 和 Find 在 Gorm 中的接口定义
FInd
// gorm/finisher_api.go
// Find find records that match given conditions
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB) {
tx = db.getInstance()
if len(conds) > 0 {
if exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...); len(exprs) > 0 {
tx.Statement.AddClause(clause.Where{Exprs: exprs})
}
}
tx.Statement.Dest = dest
tx.callbacks.Query().Execute(tx)
return
}
Go
Execute 方法会执行 Query() callback 中的回调函数,回调函数中是它真正的执行逻辑,下面是 Query Callback 的初始注册函数,:
// gorm/callbacks/callbacks.go
queryCallback := db.Callback().Query()
queryCallback.Register("gorm:query", Query)
queryCallback.Register("gorm:preload", Preload)
queryCallback.Register("gorm:after_query", AfterQuery)
Go
Query 函数里面是具体执行的逻辑:
// gorm/callbacks/query.go
func Query(db *gorm.DB) {
if db.Error == nil {
BuildQuerySQL(db)
if !db.DryRun && db.Error == nil {
// 执行数据库语句返回对应结果的行
rows, err := db.Statement.ConnPool.QueryContext(db.Statement.Context, db.Statement.SQL.String(), db.Statement.Vars...)
if err != nil {
db.AddError(err)
return
}
defer rows.Close()
// 扫描所有返回的结果行,把数据反序列化到目标结构中
gorm.Scan(rows, db, false)
}
}
Go
Scan 方法
// gorm/finisher_api.go
// Scan scan value to a struct
func (db *DB) Scan(dest interface{}) (tx *DB) {
config := *db.Config
currentLogger, newLogger := config.Logger, logger.Recorder.New()
config.Logger = newLogger
tx = db.getInstance()
tx.Config = &config
if rows, err := tx.Rows(); err != nil {
tx.AddError(err)
} else {
defer rows.Close()
if rows.Next() {
tx.ScanRows(rows, dest)
} else {
tx.RowsAffected = 0
}
}
currentLogger.Trace(tx.Statement.Context, newLogger.BeginAt, func() (string, int64) {
return newLogger.SQL, tx.RowsAffected
}, tx.Error)
tx.Logger = currentLogger
return
}
Go
这个方法里面主要有两个逻辑一个是获取对应的结果行 rows, err := tx.Rows() 另一个是对结果行做扫描反序列化 tx.ScanRows(rows, dest)
首先来看下 rows, err := tx.Rows() :
// gorm/finisher_api.go
func (db *DB) Rows() (*sql.Rows, error) {
tx := db.getInstance().InstanceSet("rows", true)
tx.callbacks.Row().Execute(tx)
rows, ok := tx.Statement.Dest.(*sql.Rows)
if !ok && tx.DryRun && tx.Error == nil {
tx.Error = ErrDryRunModeUnsupported
}
return rows, tx.Error
}
Go
它的主要执行逻辑是 tx.callbacks.Row().Execute(tx) ,Execute 方法会执行 Row callback 中的回调函数,回调函数中是它真正的执行逻辑,下面是 Row 的初始化回调函数
// gorm/callbacks/callbacks.go
db.Callback().Row().Register("gorm:row", RowQuery)
db.Callback().Raw().Register("gorm:raw", RawExec)
Go
主要的逻辑在 RowQuery 函数中
// gorm/callbacks/row.go
func RowQuery(db *gorm.DB) {
if db.Error == nil {
BuildQuerySQL(db)
if !db.DryRun {
if isRows, ok := db.InstanceGet("rows"); ok && isRows.(bool) {
db.Statement.Dest, db.Error = db.Statement.ConnPool.QueryContext(db.Statement.Context, db.Statement.SQL.String(), db.Statement.Vars...)
} else {
db.Statement.Dest = db.Statement.ConnPool.QueryRowContext(db.Statement.Context, db.Statement.SQL.String(), db.Statement.Vars...)
}
db.RowsAffected = -1
}
}
}
Go
这里我们可以看到这个 RowQuery 函数和 Find 回调的 Query 函数逻辑几乎差不多,唯一的差别就是将结果 rows 赋值给了 Statement 里面的 Dest
Scan 方法中的另一个操作是执行 tx.ScanRows(rows, dest)
// gorm/finisher_api.go
func (db *DB) ScanRows(rows *sql.Rows, dest interface{}) error {
tx := db.getInstance()
if err := tx.Statement.Parse(dest); !errors.Is(err, schema.ErrUnsupportedDataType) {
tx.AddError(err)
}
tx.Statement.Dest = dest
tx.Statement.ReflectValue = reflect.ValueOf(dest)
for tx.Statement.ReflectValue.Kind() == reflect.Ptr {
tx.Statement.ReflectValue = tx.Statement.ReflectValue.Elem()
}
Scan(rows, tx, true)
return tx.Error
}
Go
这个方法将 Statement 里面的 Dest 值改为 Scan 方法中的 Dest 结构,然后执行 Scan 函数 这个和 Find 函数 Query() 回调函数中的逻辑是一样的。
那么我们最后来看看到底 Scan 方法和 Find 方法有什么不同:
Find 在调用 Execute() 然后执行回调函数前执行了 tx.Statement.Dest = dest 修改了语句的目标 Struct,而 Scan 没有,Scan 方法是在执行完 Execute() 里面的回调函数后,在 ScanRows 方法里面调用tx.Statement.Dest = dest 。会有一个结果就是 Execute 方法会调用Parse 方法解析 Dest 或 Model 中的 struct 来得到数据库表的 Schema,而显然 Scan 方法是解析不到的。所以使用 Scan 方法的时候需要我们显示指定数据库的表名。
回调函数注册的不一样,Find 函数支持更多的 Callback 注入
queryCallback.Register("gorm:preload", Preload)
queryCallback.Register("gorm:after_query", AfterQuery)
Go