GORM何时抛出ErrRecordNotFound错误
写在前面
前一段时间的工作主要以业务逻辑编写为主,重度使用了一些Go的框架,比如Web框架Gin、ORM框架GORM、模板渲染框架Simplate等等。
对一个框架的细节了解的越多,使用的时候就能够更加得心应手。本篇博文将通过示例及源码解读的方式对GORM中的ErrRecordNotFound进行探究,其中会涉及到一些query相关的内容,比如Find、First、Last等方法的应用。
适应人群
入门√——初级——中级——高级;本文适应入门及以上。
GORM何时抛出ErrRecordNotFound错误
首先看一段示例代码
package main
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
type Topic struct {
gorm.Model
Title string
Content string
}
func main() {
db, err := gorm.Open("sqlite3", "test.db")
if err != nil {
panic("连接数据库失败")
}
// db.LogMode(true)
defer db.Close()
// 自动迁移模式,会自动在数据库中生成topics表
// 以及对应的列(id, title, content,created_at,
// updated_at, deleted_at)
db.AutoMigrate(&Topic{})
// 创建一条记录
db.Create(&Topic{Title: "welcome", Content: "jingwei.link"})
// 查找一个topic
// 这里的err值不为空,会报出“record not found”的错误
var topic Topic
err = db.Where("id=?", 0).Find(&topic).Error
fmt.Println("error:", err)
fmt.Println("topic的id:", topic.ID)
// First
err = db.Where("id=?", 0).First(&topic).Error
fmt.Println("error:", err)
fmt.Println("topic的id:", topic.ID)
// Last
err = db.Where("id=?", 0).Last(&topic).Error
fmt.Println("error:", err)
fmt.Println("topic的id:", topic.ID)
// 查找topic列表
// 这里的err值为nil
var topics []Topic
err = db.Where("id=?", 0).Find(&topics).Error
fmt.Println("error:", err)
fmt.Println("topics的长度", len(topics))
// First
err = db.Where("id=?", 0).First(&topics).Error
fmt.Println("error:", err)
fmt.Println("topics的长度", len(topics))
// Last
err = db.Where("id=?", 0).Last(&topics).Error
fmt.Println("error:", err)
fmt.Println("topics的长度", len(topics))
}
上面的示例代码,按照步骤依次:1)初始化数据库连接,这里连接到本地的sqlite数据库)。2)数据库自动表结构创建,AutoMigrate
方法可以根据传入的参数反射出对象的结构,从而根据规则映射出数据库中应该创建的表结构;比如传入&Topic{}
会自动在数据库中生成topics
表,包含id, title, content,created_at, updated_at, deleted_at
等数据列。3)创建一条Topic
记录。4)查找一条id
为0
的Topic记录。5)查找id
为0
的Topic列表。
这里特别说明一下,err = db.Where("id=?", 0).Find(&topic).Error
这种在查询语句最后添加.Error
的方式,可以把GORM查询数据库过程中发生的错误赋值到err
,从而可以通过判定err
的方式来决定接下来的操作。比如,当err
为nil
时,说明没有出现错误,代码可以正常运行;当err
不为nil
时,说明出现错误,可以直接抛出这个错误或者采取措施处理这个错误。
示例代码的运行结果分析
示例代码的运行结果如下面所示,其中record not found
是GORM中ErrRecordNotFound
这个类型的Error
的描述:
error: record not found
topic的id: 0
error: record not found
topic的id: 0
error: record not found
topic的id: 0
error: <nil>
topics的长度 0
error: <nil>
topics的长度 0
error: <nil>
topics的长度 0
GORM何时抛出ErrRecordNotFound错误
既然存在ErrRecordNotFound
这个类型的错误,很容易想到通过判定ErrRecordNotFound
是否为nil来判定数据库中是否存在某个检索条件的记录。那么GORM会在什么时候抛出ErrRecordNotFound
呢?
通过上面的示例及其运行结果可以知道,相同的检索条件Where("id=?", 0)
,传入的接收检索结果的参数类型不同得到的结果也不同。
- 当传入
var topic Topic
时抛出了ErrRecordNotFound
错误; - 当传入
var topics []Topic
时不会抛出ErrRecordNotFound
错误。
GORM相关源码阅读
GORM中的Find、First、Last方法
通过源码可以看出来,在GORM中,First与Last相比Find多了Limit限制和默认排序顺序,三个方法没有本质的区别。
// Find find records that match given conditions
func (s *DB) Find(out interface{}, where ...interface{}) *DB {
return s.NewScope(out).inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
}
// 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
}
// Last find last record that match given conditions, order by primary key
func (s *DB) Last(out interface{}, where ...interface{}) *DB {
newScope := s.NewScope(out)
newScope.Search.Limit(1)
return newScope.Set("gorm:order_by_primary_key", "DESC").
inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
}
GORM中的queryCallback方法
通过源码可以知道,在对检索出来的数据进行解析并赋值时,如果检索到0行,且传入接收检索结果的不是一个Slice类型的变量(这时候肯定是一个Struct类型的变量),会抛出 ErrRecordNotFound 错误。
// queryCallback used to query data from database
func queryCallback(scope *Scope) {
// ...
var (
isSlice, isPtr bool
resultType reflect.Type
results = scope.IndirectValue()
)
// ...
// 传入接收检索结果的变量要么是Slice,要么是Struct
// 其他类型的变量都会报错
if kind := results.Kind(); kind == reflect.Slice {
isSlice = true
resultType = results.Type().Elem()
results.Set(reflect.MakeSlice(results.Type(), 0, 0))
if resultType.Kind() == reflect.Ptr {
isPtr = true
resultType = resultType.Elem()
}
} else if kind != reflect.Struct {
scope.Err(errors.New("unsupported destination, should be slice or struct"))
return
}
// ...
// 在进行赋值时,如果检索到0行,且传入接收检索结果的不是一个Slice类型
// 就会抛出 ErrRecordNotFound 错误
if err := rows.Err(); err != nil {
scope.Err(err)
} else if scope.db.RowsAffected == 0 && !isSlice {
scope.Err(ErrRecordNotFound)
}
}
小结
本文通过一个示例来说明record not found
的错误,并通过分析源码的方式详细阐明了GORM抛出ErrRecordNotFound
的具体场景。具体地,1)传入接收检索结果的变量只能为Struct类型或Slice类型;2)当传入变量为Struc类型时,如果检索出来的数据为0条,会抛出ErrRecordNotFound
错误;3)当传入变量为Slice类型时,任何条件下均不会抛出ErrRecordNotFound
错误。