gorm注册callback原理

背景:

在做项目时,遇到一个监控需求,就是每次执行sql语句都要上报耗时到prometheus。首先想到的是在写业务逻辑时每次执行sql语句都自己上报,但这样会侵入业务代码,而且有的人会忘了上报,或者上报格式不规范,所以想到将上报逻辑封装到gorm的框架里。

具体原理:

gorm执行sql语句都是通过注册callback函数实现的。如执行如下语句时:

image

会依次执行、queryCallback、queryCallback、queryCallback,这是因为gorm初始化时注册了这几个函数:

image

如果我们想注册些自己写的函数呢?而且想控制函数调用的顺序,比如想自己注册一个函数func2,而且在queryCallback之后,queryCallback之前调用,那改怎么办?其实可以这样:

image

其中after函数的参数就是注册queryCallback时指定的名字,Register的第一个参数callbackName就是你要注册的函数的名字,callbackName要保持唯一性,否则后面Register的会覆盖前面的。

至此,调first函数时,将会依次调这几个函数:queryCallback、func、queryCallback、queryCallback

源码分析:

callback的定义。上面的first语句会把callback func append到queries字段里。

// Callback is a struct that contains all CRUD callbacks
//   Field `creates` contains callbacks will be call when creating object
//   Field `updates` contains callbacks will be call when updating object
//   Field `deletes` contains callbacks will be call when deleting object
//   Field `queries` contains callbacks will be call when querying object with query methods like Find, First, Related, Association...
//   Field `rowQueries` contains callbacks will be call when querying object with Row, Rows...
//   Field `processors` contains all callback processors, will be used to generate above callbacks in order
type Callback struct {
    logger     logger
    creates    []*func(scope *Scope)
    updates    []*func(scope *Scope)
    deletes    []*func(scope *Scope)
    queries    []*func(scope *Scope)
    rowQueries []*func(scope *Scope)
    processors []*CallbackProcessor
}

这个就是first的源码,最后一行调callCallbacks时就会依次调queries的func。

// First find first record that match given conditions, order by primary key
func (s *DB) First(out interface{}, where ...interface{}) *DB {
    newScope := s.NewScope(out)
    newScope.Search.Limit(1)

    return newScope.Set("gorm:order_by_primary_key", "ASC").
        inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
}
func (scope *Scope) callCallbacks(funcs []*func(s *Scope)) *Scope {
    defer func() {
        if err := recover(); err != nil {
            if db, ok := scope.db.db.(sqlTx); ok {
                db.Rollback()
            }
            panic(err)
        }
    }()
    for _, f := range funcs {
        (*f)(scope)
        if scope.skipLeft {
            break
        }
    }
    return scope
}

参考文献:

http://gorm.book.jasperxu.com/callbacks.html

https://gorm.io/docs/hooks.html

Gin实践 连载十 定制 GORM Callbacks

你可能感兴趣的:(gorm注册callback原理)