1. 测试代码
type Topic struct {
gorm.Model
Title string
Content string
}
func main() {
db, err := gorm.Open("sqlite3", "test.db")
if err != nil {
panic("Unable to connect to the database")
}
// db.LogMode(true)
defer db.Close()
// Automatic migration mode, which will automatically generate the topics table in the database
// and the corresponding column(id, title, content,created_at,
// updated_at, deleted_at)
db.AutoMigrate(&Topic{})
// Create a record
db.Create(&Topic{Title: "welcome", Content: "foo"})
// find a topic
// The err value here is not empty, and the error "record not found" will be reported
var topic Topic
err = db.Where("id=?", 0).Find(&topic).Error
fmt.Println("error:", err)
fmt.Println("id of the topic:", topic.ID)
// First
err = db.Where("id=?", 0).First(&topic).Error
fmt.Println("error:", err)
fmt.Println("id of the topic:", topic.ID)
// Last
err = db.Where("id=?", 0).Last(&topic).Error
fmt.Println("error:", err)
fmt.Println("id of the topic:", topic.ID)
// Find a list of topics
// The err value here is nil
var topics []Topic
err = db.Where("id=?", 0).Find(&topics).Error
fmt.Println("error:", err)
fmt.Println("length of topics", len(topics))
// First
err = db.Where("id=?", 0).First(&topics).Error
fmt.Println("error:", err)
fmt.Println("length of topics", len(topics))
// Last
err = db.Where("id=?", 0).Last(&topics).Error
fmt.Println("error:", err)
fmt.Println("length of topics", len(topics))
}
2. 代码流程和结果
- 初始化数据库连接,这里连接到本地 SQLite 数据库。
- 自动创建数据库表结构,AutoMigrate 方法可以根据输入的参数反映对象的结构,从而根据规则映射应该在数据库中创建的表结构; 例如,传入 &topic {}将自动生成数据库中的主题表,包括 id、 title、 content、 create _ at、 update _ at、 delete _ at 等数据列。
- 创建一个 Topic 记录
- 查找 id 为0的 Topic 记录
- 查找 id 为0的主题列表
error: record not found
id of the topic: 0
error: record not found
id of the topic: 0
error: record not found
id of the topic: 0
error: <nil>
length of topics 0
error: <nil>
length of topics 0
error: <nil>
length of topics 0
3. source code
// 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
}
// queryCallback used to query data from database
func queryCallback(scope *Scope) {
// ...
var (
isSlice, isPtr bool
resultType reflect.Type
results = scope.IndirectValue()
)
// ...
// The variable passed in to receive the retrieval result is either Slice or Struct
// Other types of variables will report an error
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
}
// ...
// When assigning, if 0 rows are retrieved, and the received retrieval result is not a Slice type
// will throw ErrRecordNotFound error
if err := rows.Err(); err != nil {
scope.Err(err)
} else if scope.db.RowsAffected == 0 && !isSlice {
scope.Err(ErrRecordNotFound)
}
}
从源代码可以看出,在 GORM 中,First 和 Last 比 Find 有更多的限制和默认排序顺序,这三种方法之间没有本质区别。
GORM 中的 queryCallback 方法。从源代码中可以知道,当分析和分配检索到的数据时,如果检索到0行,并且接收到的检索结果不是 Slice 类型的变量(此时它必须是 Sstruct 类型的变量) ,它将抛出 ErrRecordNotfound 错误。
4. 小结
本文通过一个例子说明了记录未找到的错误,并通过分析源代码详细说明了 GORM 抛出的 ErrRecordNotFind 的具体场景。
- 传入以接收检索结果的变量只能是 Struct 类型或 Slice 类型。
- 当传入变量为 Struct 类型时,如果检索到的数据为0,则会抛出 ErrRecordNotfound 错误。
- 当传入变量的类型为 Slice 时,在任何情况下都不会引发 ErrRecordNotfound 错误。