как горм обрабатывает пустой запрос

Go

предисловие

gormЭто фреймворк для языка go.

Обработка Gorm пустых результатов запроса в основном отражается в gorm.ErrRecordNotFound, который является предопределенным типом ошибки. Буквально фреймворк вернет эту ошибку, когда данные не могут быть запрошены, но в реальной ситуации есть еще одна загадка.

В этой статье будут представлены следующие три аспекта:

  • Все методы запроса возвращают этот тип ошибки
  • Как бороться с этой ошибкой
  • Итак, почему вам нужно определить этот тип

место действия

gorm.ErrRecordNotFound появляется не во всех сценариях запросов. Он должен сопоставлять определенные условия запроса с конкретными методами запроса. Давайте посмотрим на исходный код по очереди👀

gorm зарегистрирует функцию обратного вызова запроса queryCallback по умолчанию

 // Define callbacks for querying

func init() {

   DefaultCallback.Query().Register("gorm:query", queryCallback)

   // ...

}

В конце функции есть такой абзац

if scope.db.RowsAffected == 0 && !isSlice {

   scope.Err(ErrRecordNotFound)

}

Видно, что только когда результат запроса пуст и переменная, используемая для получения результата, не является типом слайса (то есть одиночной структурой), ошибка устанавливается в область видимости

Логику также можно найти в определенных аннотациях

// ErrRecordNotFound record not found error, happens when haven't find any matched data when looking up with a struct
var ErrRecordNotFound = gorm.ErrRecordNotFound

Итак, какие методы запроса будут выполнять метод обратного вызова? Благодаря отслеживанию исходного кода видно, что существуют только следующие четыре метода запроса.

 // First find first record that match given conditions, order by primary key

func (s *DB) First(out interface{}, where ...interface{}) *DB {}



 // Last find last record that match given conditions, order by primary key

func (s *DB) Last(out interface{}, where ...interface{}) *DB {}



 // Find find records that match given conditions

func (s *DB) Find(out interface{}, where ...interface{}) *DB {}



 // Scan scan value to a struct

func (s *DB) Scan(dest interface{}) *DB {}

Итак, краткое изложение того, в каких сценариях возникает эта ошибка, выглядит следующим образом:

  • Запрос с использованием одного из четырех методов запроса First, Last, Find, Scan(Для code.byted.org/gopkg/gorm v1.0.4 другие версии могут отличаться)

    • Другие методы запроса, такие как метод Pluck, не должны появляться.Конечно, метод Pluck ограничивает принимающую переменную типом среза.
  • Результат запроса пуст, а обход, использованный для получения, не является срезом.

    • Обратите внимание, что вышеупомянутые четыре метода не ограничивают принимающий объект от того, чтобы он был срезом, то есть вы можете использовать срез в качестве принимающей переменной для вызова метода First, хотя это не соответствует первоначальному намерению метода First для проверки первый.
    • В отличие от общей идеи, это не означает, что ошибка gorm.ErrRecordNotFound будет возвращена, если первый запрос не может быть использован, и ошибка gorm.ErrRecordNotFound не будет возвращена, если она не может быть найдена с помощью Find, но выполняется комплексная обработка в соответствии с полученным типом переменной.

как с этим бороться

Когда фреймворк вернется, нужно подумать, как с ним бороться

Во-первых, метод запроса, который вряд ли вернет этот тип ошибки, не нуждается в обработке.

Например, следующее суждение об ошибке является излишним, поскольку условиеневозможно

var emps []*model.Employee

if err := LocalWriteDB(ctx).Where("user_id=?", userId).Find(&emps).Error; err == nil {

   return authList, nil

 // 不必要的判断!  

} else if err == gorm.ErrRecordNotFound {

   return emps, nil

} else {

   return nil, err

}

Затем нам нужно рассмотреть, можно ли найти данные, ожидаемые в бизнесе, является ли это нормальной ситуацией в пределах ожидания или ненормальной ситуацией за пределами ожидания, большинство случаев являются нормальными ситуациями, и верхний уровень должен воспринимать больше системные ошибки, такие как аномалии соединения

Если данные не существуют и возвращается ошибка, это может вызвать проблемы у пользователя.Например, то, что нужно пользователю, этосписок пуст, но здесь это рассматривается как исключение, и пользователь получитИсключение сервераподсказки

Следовательно, вы можете просто установить для возвращаемого результата и ошибки значение nil следующим образом:

emp := &model.Employee{}

if err := LocalWriteDB(ctx).Where("user_id=?", userId).First(&emps).Error; err == nil {

   return authList, nil 

} else if err == gorm.ErrRecordNotFound {

   return nil, nil

} else {

   return nil, err

}

Если верхнему уровню необходимо воспринять тип ошибки, если gorm.ErrRecordNotFound возвращается как есть, это приведет к тому, что верхний уровень будет полагаться на внешний gorm в дополнение к уровню dal, который слишком зависим.Не подходит для постобслуживания. Следовательно, новый тип ошибки может быть определен на уровне dal для возврата (например, dal.RecordNotFound), чтобы внешний уровень зависел только от уровня dal, а уровень dal защищал зависимость верхнего уровня от внешних конкретных инструментов.

дизайн-мышление

Существуют фреймворки orm на других языках, которые не определяют подобные ошибки, например mybatis, код запроса одной записи выглядит следующим образом

public <T> T selectOne(String statement, Object parameter) {

    List<T> list = this.selectList(statement, parameter);

    if (list.size() == 1) {

        return list.get(0);

    } else if (list.size() > 1) {

        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());

    } else {

        return null;

    }

}

Можно видеть, что если бизнес-код ожидает запроса одной записи и не может быть запрошен, он напрямую вернет значение null.

gorm отличается от mybatis, потому что gorm использует метод реализации заполнения результатов запроса в принимающих переменных, указанных пользователем, аналогичный стилю кода C, и помещает данные для изменения в параметры (beego-orm также реализован в этом случае)

Если полученная переменная имеет тип среза, запись можно подтвердить, определив, что len(slice) == 0, но если полученная переменная является одной структурой, пользователь не может судить (из-за существования ссылки она сторона пользователя не может записать ссылку на объект, установленную на nil), поэтому, установив err для определенного gorm.ErrRecordNotFound, он будет передан пользователю для обработки суждения.

Вернемся к теме, как gorm обрабатывает возвращаемый результат пуст:

  1. Метод запроса является одним из указанных методов, а принимающая переменная не является срезом, а результат запроса к базе данных пуст, представлен err = gorm.ErrRecordNotFound.
  2. Если принимающая переменная является срезом, а результат запроса к базе данных пуст, он оценивается в зависимости от того, равна ли длина среза 0.

Суммировать

Задача при использовании gorm всегда заключалась в том, чтобы думать, что gorm.ErrRecordNotFound связан только с конкретным методом, но на самом деле он связан с принимающим типом. В этой статье анализируется сцена ошибки из исходного кода, рассказывается о том, как разумно справляться с ошибкой, и о дизайнерском мышлении.