В моем впечатлении неправильное восприятие: если GORM не найдет запись, она вернетсяErrRecordNotFound
Я знал, что в последнем бизнесе была ошибка, и я обнаружил, что восприятие в этом впечатлении было неправильным, и официальной документации, подтверждающей это, не было. Так,ErrRecordNotFound
Когда он вернется?В этой статье будет проанализирован исходный код.
demo
Сначала давайте посмотрим на пример, а затем угадаем напечатанный результат.
package main
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
type User struct {
ID int64 `gorm:"column:id;primary_key" json:"id"`
Name string `gorm:"column:name" json:"name"`
Email string `gorm:"column:email" json:"email"`
}
func (user *User) TableName() string {
return "ranger_user"
}
func main() {
db := open()
user := &User{}
users := make([]*User, 0, 0)
err := db.Model(user).Where("id = ?", 1).First(user).Error
fmt.Println(err, user)
err = db.Model(user).Where("id = ?", 1).Find(&users).Error
fmt.Println(err, user)
}
func open() *gorm.DB {
db, err := gorm.Open("mysql",
"user:pass@(ip:port)/db?charset=utf8&parseTime=True&loc=Local")
if err != nil {
panic(err)
}
return db
}
результат:
record not found &{0 }
<nil> &{0 }
Подводя итог, можно обнаружить, что,First()
Когда функция не может найти запись, она возвращаетErrRecordNotFound
, иFind()
Возвращает nil, ну вот и конец этой статьи
Конечно, приведенная выше фраза - шутка. Я не хедлайнер, и у меня нет галантереи. Как я могу так стесняться говорить здесь ерунду? Начнем гоняться за исходным кодом.
структура
Вот некоторые структуры данных, которые можно использовать позже.Это легко понять, поместив оттиск на лицевой стороне.
DB
// DB contains information for current db connection
type DB struct {
sync.RWMutex
Value interface{} // 这里存放的model结构体,可通过结构体的方法,找到需要查询的表
Error error // 出错信息
RowsAffected int64 // sql返回的 rows affected
// single db
db SQLCommon // db信息,这里是最小数据库连接所需用到的函数的interface
blockGlobalUpdate bool
logMode logModeValue
logger logger
search *search // where order group 等条件存放的地方
values sync.Map // 数据结果集
// global db
parent *DB // parent db, gorm的大部分函数,都会clone一个自身,然后把被clone的对象放在这里
callbacks *Callback // 回调函数
dialect Dialect
singularTable bool
// function to be used to override the creating of a new timestamp
nowFuncOverride func() time.Time
}
Scope
Структура области действия записывает все текущие операции в базе данных.
type Scope struct {
Search *search
Value interface{} // 用户通过First,Find 传入的结果容器变量
SQL string
SQLVars []interface{}
db *DB
instanceID string
primaryKeyField *Field
skipLeft bool
fields *[]*Field
selectAttrs *[]string
}
SQLCommon
// SQLCommon is the minimal database connection functionality gorm requires. Implemented by *sql.DB.
type SQLCommon interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Prepare(query string) (*sql.Stmt, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
}
search
Здесь записываются операторы поиска для условий sql, и, наконец, sql сплайсируется.
type search struct {
db *DB
whereConditions []map[string]interface{}
orConditions []map[string]interface{}
notConditions []map[string]interface{}
havingConditions []map[string]interface{}
joinConditions []map[string]interface{}
initAttrs []interface{}
assignAttrs []interface{}
selects map[string]interface{}
omits []string
orders []interface{}
preload []searchPreload
offset interface{}
limit interface{}
group string
tableName string
raw bool
Unscoped bool
ignoreOrderQuery bool
}
Callback
Функция обратного вызова каждого запроса записывается.После завершения соответствующего запроса будет вызвана соответствующая функция обратного вызова.
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
}
CallbackProcessor
Подробная информация об обратном вызове, согласно информации, обратный вызов может быть отсортирован, а затем помещен вCallback
структураcreates
среди прочего имущества
// CallbackProcessor contains callback informations
type CallbackProcessor struct {
logger logger
name string // current callback's name
before string // register current callback before a callback
after string // register current callback after a callback
replace bool // replace callbacks with same name
remove bool // delete callbacks with same name
kind string // callback type: create, update, delete, query, row_query
processor *func(scope *Scope) // callback handler
parent *Callback
}
новое соединение
Первым шагом в запросе является установление соединения
func Open(dialect string, args ...interface{}) (db *DB, err error) {
if len(args) == 0 {
err = errors.New("invalid database source")
return nil, err
}
var source string
var dbSQL SQLCommon
var ownDbSQL bool
switch value := args[0].(type) {
case string:
// 根据 dialect 判断数据库驱动类型
var driver = dialect
if len(args) == 1 {
source = value
} else if len(args) >= 2 {
driver = value
source = args[1].(string)
}
// 调用底层的database.sql 来建立一个sql.DB
dbSQL, err = sql.Open(driver, source)
ownDbSQL = true
case SQLCommon:
// 如果原先就是 SQLCommon interface,直接拿过来使用即可
dbSQL = value
ownDbSQL = false
default:
return nil, fmt.Errorf("invalid database source: %v is not a valid type", value)
}
db = &DB{
db: dbSQL,
logger: defaultLogger,
callbacks: DefaultCallback,
dialect: newDialect(dialect, dbSQL),
}
db.parent = db
if err != nil {
return
}
// Send a ping to make sure the database connection is alive.
// 发个ping,确保连接有效
if d, ok := dbSQL.(*sql.DB); ok {
if err = d.Ping(); err != nil && ownDbSQL {
d.Close()
}
}
return
}
Видно, что gorm.Open также вызывает sql.Open, предоставленный go, для установления ссылки и, наконец, ping, чтобы убедиться, что ссылка действительна.
Запрос
функция запроса
После установления связи запрос будет запущен позже, и каждая функция в запросе будет проанализирована одна за другой.
func (s *DB) Model(value interface{}) *DB {
// 首先clone自身,确保使用了函数后,不会影响原先的变量
c := s.clone()
// 把传过来的结构体,设给Value
c.Value = value
return c
}
// 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
func (s *DB) Where(query interface{}, args ...interface{}) *DB {
return s.clone().search.Where(query, args...).db
}
// search.Where
func (s *search) Where(query interface{}, values ...interface{}) *search {
// 把搜索条件追加到search的 whereConditions 属性里面
s.whereConditions = append(s.whereConditions, map[string]interface{}{"query": query, "args": values})
return s
}
First
// 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
}
// NewScope create a scope for current operation
// 创建一个新的scope结构体,并记录当前的所有操作
func (s *DB) NewScope(value interface{}) *Scope {
dbClone := s.clone()
dbClone.Value = value
scope := &Scope{db: dbClone, Value: value}
if s.search != nil {
scope.Search = s.search.clone()
} else {
scope.Search = &search{}
}
return scope
}
// 记录追加当前操作的条件语句
func (scope *Scope) inlineCondition(values ...interface{}) *Scope {
if len(values) > 0 {
scope.Search.Where(values[0], values[1:]...)
}
return scope
}
Find
Логика метода Find очень похожа на логику метода First: First добавляет Limit(1), а Find — нет.
// 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
}
Выполнив здесь, мы обнаружили, что задали условие запроса sql, но, похоже, sql еще не выполнен, остался только одинcallCallbacks
Исполнения нет.Возможно ли склеить sql и выполнить его в этой функции?Продолжим исследовать позже
callCallbacks
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
}
Вот и все, тогдаcallCallbacks
Что именно он сделал?
хочу знатьcallCallbacks
Что именно выполняется, мы должны видеть, звонитьcallCallbacks
, что пришло
s.NewScope(out).inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
s.parent.callbacks.queries
Это, кажется, никогда не видел места с назначением, то толькоinit()
Чтобы объяснить, тогда давайте посмотрим, что делает инициализация горма
инициализация горма
callback_query.go
func init() {
DefaultCallback.Query().Register("gorm:query", queryCallback)
DefaultCallback.Query().Register("gorm:preload", preloadCallback)
DefaultCallback.Query().Register("gorm:after_query", afterQueryCallback)
}
callback_delete.go
func init() {
DefaultCallback.Delete().Register("gorm:begin_transaction", beginTransactionCallback)
DefaultCallback.Delete().Register("gorm:before_delete", beforeDeleteCallback)
DefaultCallback.Delete().Register("gorm:delete", deleteCallback)
DefaultCallback.Delete().Register("gorm:after_delete", afterDeleteCallback)
DefaultCallback.Delete().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback)
}
Подождите, create и update имеют соответствующие функции инициализации, поэтому мы не будем продолжать расширяться здесь.
зарегистрировать обратный вызов
// Query could be used to register callbacks for querying objects with query methods like `Find`, `First`, `Related`, `Association`...
// Refer `Create` for usage
// 创建一个CallbackProcessor 的结构体,在这个结构体上注册信息
func (c *Callback) Query() *CallbackProcessor {
return &CallbackProcessor{logger: c.logger, kind: "query", parent: c}
}
// Register a new callback, refer `Callbacks.Create`
func (cp *CallbackProcessor) Register(callbackName string, callback func(scope *Scope)) {
if cp.kind == "row_query" {
if cp.before == "" && cp.after == "" && callbackName != "gorm:row_query" {
cp.logger.Print(fmt.Sprintf("Registering RowQuery callback %v without specify order with Before(), After(), applying Before('gorm:row_query') by default for compatibility...\n", callbackName))
cp.before = "gorm:row_query"
}
}
//继续赋值CallbackProcessor的属性,并把CallbackProcessor结构体追加到processors slice中
cp.name = callbackName
cp.processor = &callback
cp.parent.processors = append(cp.parent.processors, cp)
// 通过record,区分是 create update,并追加到不同的属性上
cp.parent.reorder()
}
// reorder all registered processors, and reset CRUD callbacks
func (c *Callback) reorder() {
var creates, updates, deletes, queries, rowQueries []*CallbackProcessor
for _, processor := range c.processors {
if processor.name != "" {
switch processor.kind {
case "create":
creates = append(creates, processor)
case "update":
updates = append(updates, processor)
case "delete":
deletes = append(deletes, processor)
case "query":
queries = append(queries, processor)
case "row_query":
rowQueries = append(rowQueries, processor)
}
}
}
// 根据CallbackProcessor 的before after 等信息,最这个slice进行排序,以便顺序调用
c.creates = sortProcessors(creates)
c.updates = sortProcessors(updates)
c.deletes = sortProcessors(deletes)
c.queries = sortProcessors(queries)
c.rowQueries = sortProcessors(rowQueries)
}
На данный момент мы увидели, как регистрируются эти функции обратного вызова, а затем давайте посмотрим, что делает функция обратного вызова, соответствующая Query, чтобы вызвать возврат First.ErrRecordNotFound
, и разница в том, что Find возвращает ноль
Перезвони
func queryCallback(scope *Scope) {
if _, skip := scope.InstanceGet("gorm:skip_query_callback"); skip {
return
}
//we are only preloading relations, dont touch base model
if _, skip := scope.InstanceGet("gorm:only_preload"); skip {
return
}
defer scope.trace(scope.db.nowFunc())
var (
isSlice, isPtr bool
resultType reflect.Type
results = scope.IndirectValue()
)
// 判断是否有根据primary key 排序的设置,有的话,追加order排序条件
if orderBy, ok := scope.Get("gorm:order_by_primary_key"); ok {
if primaryField := scope.PrimaryField(); primaryField != nil {
scope.Search.Order(fmt.Sprintf("%v.%v %v", scope.QuotedTableName(), scope.Quote(primaryField.DBName), orderBy))
}
}
if value, ok := scope.Get("gorm:query_destination"); ok {
results = indirect(reflect.ValueOf(value))
}
// 判断接收数据的变量的类型,并进行处理
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
}
// 拼接sql
scope.prepareQuerySQL()
if !scope.HasError() {
scope.db.RowsAffected = 0
if str, ok := scope.Get("gorm:query_option"); ok {
scope.SQL += addExtraSpaceIfExist(fmt.Sprint(str))
}
// 执行拼接好的sql
if rows, err := scope.SQLDB().Query(scope.SQL, scope.SQLVars...); scope.Err(err) == nil {
defer rows.Close()
columns, _ := rows.Columns()
for rows.Next() {
// 记录RowsAffected
scope.db.RowsAffected++
elem := results
if isSlice {
elem = reflect.New(resultType).Elem()
}
scope.scan(rows, columns, scope.New(elem.Addr().Interface()).Fields())
// 数据追加到接收的结果集里面
if isSlice {
if isPtr {
results.Set(reflect.Append(results, elem.Addr()))
} else {
results.Set(reflect.Append(results, elem))
}
}
}
// 判断是否有错,有错误,就返回错误信息
if err := rows.Err(); err != nil {
scope.Err(err)
} else if scope.db.RowsAffected == 0 && !isSlice {
// 如果RowsAffected == 0,也就是没有数据,且接收的结果集不是 slice,就报 ErrRecordNotFound 的错误
scope.Err(ErrRecordNotFound)
}
}
}
}
На данный момент мы видим, что это не имеет ничего общего с функцией Find или First, а с переменной, которая передается для получения результата.Если переменная полученного результата является срезом, он не будет сообщен.ErrRecordNotFound
дальнейшая проверка
Давайте изменим основную функцию в демо
func main() {
db := open()
user := &User{}
users := make([]*User, 0, 0)
err := db.Model(user).Where("id = ?", 1).First(&users).Error
db.Table(user.TableName()).Model(user)
fmt.Println(err, user)
err = db.Model(user).Where("id = ?", 1).Find(user).Error
fmt.Println(err, user)
}
В это время по результатам, полученным путем отслеживания исходного кода, он должен возвращать
<nil> &{0 }
record not found &{0 }
Результат напечатан следующим образом
<nil> &{0 }
record not found &{0 }
идеально
Суммировать
Переменная, переданная в полученный результирующий набор, может быть только типа Struct или типа Slice. Если входящая переменная имеет тип Struc, если извлеченные данные равны 0, будет выдана ошибка ErrRecordNotFound. Когда входящая переменная имеет тип Slice, любая ошибка ErrRecordNotFound не будет выдана ни при каких условиях