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)查找一条id0的Topic记录。5)查找id0的Topic列表。

这里特别说明一下,err = db.Where("id=?", 0).Find(&topic).Error这种在查询语句最后添加.Error的方式,可以把GORM查询数据库过程中发生的错误赋值到err,从而可以通过判定err的方式来决定接下来的操作。比如,当errnil时,说明没有出现错误,代码可以正常运行;当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),传入的接收检索结果的参数类型不同得到的结果也不同。

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错误。

参考