ErrRecordNotFound запись карьера GORM

Go

В моем впечатлении неправильное восприятие: если 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 не будет выдана ни при каких условиях