在我印象中有个错误的认知:如果GORM没有找到record,则会返回ErrRecordNotFound
的错误,知道上次业务中出现了bug,我才发现这个印象中的认知是错误的,且没有官方文档的支持。那么,ErrRecordNotFound
到底在什么时候返回呢,这篇文章将会根据源码来进行分析一下
demo
首先我们先来看一个示例,然后,猜测一下打印的结果
1 | package main |
结果:
1 | record not found &{0 } |
综上,可以发现,First()
函数找不到record的时候,会返回ErrRecordNotFound
, 而Find()
则是返回nil,好了,这篇文章就到此结束了
当然,上面一句是开玩笑的,我可不是标题党,没点干货,怎好意思在这扯淡,下面我们开始追进源码
结构
这里是后面可能会用到的一些数据结构,放在前面有个印象,便于理解
DB
1 | // DB contains information for current db connection |
Scope
scope结构体记录了当前对数据库的所有操作
1 | type Scope struct { |
SQLCommon
1 | // SQLCommon is the minimal database connection functionality gorm requires. Implemented by *sql.DB. |
search
sql条件的搜索语句都记录在这里了,最后拼接出来sql
1 | type search struct { |
Callback
记录了各个查询的回调函数,相应查询完成后,会调用对应的回调函数
1 | type Callback struct { |
CallbackProcessor
callback的详情信息,可根据这些信息,对callback进行排序,然后再放入到 Callback
结构体 creates
等属性中
1 | // CallbackProcessor contains callback informations |
新建连接
查询第一步,先建立好连接
1 | func Open(dialect string, args ...interface{}) (db *DB, err error) { |
可以看出,gorm.Open 也是调用了go提供的 sql.Open 来建立一个链接,最后ping一下,确保这个链接有效
查询
查询函数
链接建立完了,后面就开始进行查询了,逐个分析查询中的各个函数
1 | func (s *DB) Model(value interface{}) *DB { |
1 | // Where return a new relation, filter records with given conditions, accepts `map`, `struct` or `string` as conditions, refer http://jinzhu.github.io/gorm/crud.html#query |
First
1 | // First find first record that match given conditions, order by primary key |
Find
Find方法与First的逻辑很像,First增加了一个Limit(1), 而Find没有
1 | // Find find records that match given conditions |
执行到这里,我们发现,我们设置了sql的查询条件,但是貌似这个sql还没有执行呢,就剩下一个 callCallbacks
没有执行了,难道在这个函数里面,拼接sql并执行吗,后面就继续探索一下
callCallbacks
1 | func (scope *Scope) callCallbacks(funcs []*func(s *Scope)) *Scope { |
就这样,那么 callCallbacks
到底执行了啥?
想要知道callCallbacks
到底执行了什么,我们就要看看,调用callCallbacks
时,传了什么过来
1 | s.NewScope(out).inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db |
s.parent.callbacks.queries
这个好像一直没有看到有赋值的地方,那就只有 init()
来解释了,那么就看一下 gorm初始化都干了什么
gorm 初始化
callback_query.go
1 | func init() { |
callback_delete.go
1 | func init() { |
等等,create、update均有对应的init函数,这里就不继续扩展了
注册回调
1 | // Query could be used to register callbacks for querying objects with query methods like `Find`, `First`, `Related`, `Association`... |
1 | // Register a new callback, refer `Callbacks.Create` |
1 | // reorder all registered processors, and reset CRUD callbacks |
到这里,我们就看到了这些回调函数是怎么注册上来的,后面就是看一下,Query对应的回调函数,到底干了什么,才会导致 First 返回ErrRecordNotFound
, 而Find 返回nil的区别
回调
1 | func queryCallback(scope *Scope) { |
至此,我们可以看出,跟Find 还是 First函数没有太大的关系,而是跟传递的接收结果的变量有关,如果接收结果的变量时slice,那么就不会报ErrRecordNotFound
进一步验证
我们修改一下demo中的main函数
1 | func main() { |
这时候,按照我们追踪源码得到的结果,应该返回
1 | <nil> &{0 } |
打印出来结果如下
1 | <nil> &{0 } |
甚是完美
总结
传入接收结果集的变量只能为Struct类型或Slice类型,当传入变量为Struc类型时,如果检索出来的数据为0条,会抛出ErrRecordNotFound错误,当传入变量为Slice类型时,任何条件下均不会抛出ErrRecordNotFound错误