предисловие
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 обрабатывает возвращаемый результат пуст:
- Метод запроса является одним из указанных методов, а принимающая переменная не является срезом, а результат запроса к базе данных пуст, представлен err = gorm.ErrRecordNotFound.
- Если принимающая переменная является срезом, а результат запроса к базе данных пуст, он оценивается в зависимости от того, равна ли длина среза 0.
Суммировать
Задача при использовании gorm всегда заключалась в том, чтобы думать, что gorm.ErrRecordNotFound связан только с конкретным методом, но на самом деле он связан с принимающим типом. В этой статье анализируется сцена ошибки из исходного кода, рассказывается о том, как разумно справляться с ошибкой, и о дизайнерском мышлении.