Запрос для получения одной записи из базы данных через SELECT может показаться немного сложным. Давайте разберемся, как это можно выполнить используя обновленный метод SnippetModel.Get(), чтобы он возвращал определенную заметку на основе её ID.

Содержание статьи

Для этого требуется выполнить SQL запрос к базе данных:

Премиум 👑 канал по Golang

Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎

Подписаться на канал

Уроки, статьи и Видео

Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.

Go в ВК ЧАТ в Telegram

Поскольку таблица snippets использует столбец id в качестве первичного ключа, этот запрос всегда будет возвращать только одну запись из базы данных (или вообще ни одной). Запрос также включает проверку срока годности заметки, чтобы не возвращались заметки с истекшим сроком.

Обратите внимание, что мы снова используем плейсхолдер для id, это сделано в целях безопасности. Ведь, в будущем ID заметки мы получим от пользователя из URL. Это не безопасный источник для получения данных, потому мы передаём данные через плейсхолдер при выполнении SQL запроса.

Откройте файл pkg/models/mysql/snippets.go и добавьте следующий код:

На заметку: Вам может быть интересно, почему мы возвращаем ошибку models.ErrNoRecord вместо sql.ErrNoRows. Причина в том, что требуется полностью инкапсулировать модель, чтобы приложение не было связано с базовым хранилищем данных или зависело от ошибок базы данных.

Конвертирование типов из MySQL в Go

Под капотом метода rows.Scan(), драйвер базы данных автоматически преобразует MySQL типы в типы языка программирования Go:

  • CHAR, VARCHAR и TEXT соответствуют типу string;
  • BOOLEAN соответствует bool;
  • INT соответствует int;
  • BIGINT соответствует int64;
  • DECIMAL и NUMERIC соответствуют float;
  • TIME, DATE и TIMESTAMP соответствуют time.Time.

Особенность нашего MySQL драйвера заключается в том, нам нужно использовать параметр parseTime=true в нашей строке подключения к MySQL, чтобы заставить его преобразовывать поля TIME и DATE в time.Time. В противном случае, он вернёт их как объекты []byte. Это один из многих предлагаемых параметров для предварительной настройки драйвера базы данных при подключении к нему.

Модели базы данных в обработчиках

Отлично, давайте воспользуемся методом SnippetModel.Get() на практике.

Откройте файл cmd/web/handlers.go и обновите обработчик showSnippet, чтобы он возвращал данные для определенной записи:

Запускаем наше веб-приложение из терминала:

Перейдите в браузере на страницу http://127.0.0.1:4000/snippet?id=1. Вы должны увидеть HTTP ответ, похожий на следующий:

SELECT запрос в Golang

Также, можете попробовать сделать несколько запросов для других заметок, срок действия которых истек или которых вообще не существуют (например, id=99), чтобы убедиться, что они возвращают ответ 404 Not Found:

MySQL запись не найдена

Обработка ошибок используя errors.Is()

В коде выше мы использовали функцию errors.Is(), которая была введена в Go 1.13, чтобы проверить, нужная ли нам ошибка выскочила (в нашем случае проверяется, если нам попалась именно ошибка sql.ErrNoRows).

Обсудим данный вопрос немного подробнее.

Во-первых, sql.ErrNoRows является примером так называемых предопределённых ошибок (по аналогии с Python, это будут «исключения» exceptions), которых мы можем приблизительно определить как объект error, хранящийся в глобальной переменной. Обычно они создаются с помощью функции errors.New(). Несколько примеров  ошибок из стандартной библиотеки — io.ErrUnexpectedEOF и bytes.ErrTooLarge.

Только что созданная нами ошибка models.ErrNoRecord является примером предопределённой ошибки.

У нас есть её «имя» и мы можем «ловить» её в случае если она возникнет при работе программы. Всё работает по принципу try - catch в Java, PHP или try — except в Python.

В старых версиях Go (до 1.13) лучший способ проверить, если ошибка совпадает с предопределённой ошибкой, выглядел бы так:

Однако после Go 1.13 лучше использовать функцию errors.Is():

Причина в том, что в Go 1.13 появилась возможность оборачивать ошибки для добавления дополнительной информации. По сути, мы создаём новую ошибку на базе уже существующей ошибки.

Для строго стиля проверки на ошибку — предопределённая ошибка и созданная (обёрнутая) на основе неё другая новая ошибка — не будут совпадать, так как обернутая ошибка не равна оригинальной предопределённой ошибке.

Функция errors.Is() наоборот работает путем распаковки ошибок — при необходимости — перед проверкой на совпадения.

Если у вас Go 1.13 или новее, лучше использовать error.Is(). Это хороший способ защитить код в будущем и предотвратить проблемы, вызванные вами — или любыми пакетами, которые ваш код импортирует.

Существует также другая функция, errors.As(), которую вы можете использовать, чтобы проверить, есть ли (у потенциально обернутой — wrapped) ошибки определенный тип. Мы будем использовать эту функцию в будущих уроках.

Для получения дополнительной информации об изменениях в способах обработки ошибок в Go 1.13 можете прочитать официальный FAQ.

Метод для получения данных записи

Мы специально сделали код в SnippetModel.Get() немного длиннее, чтобы было проще понять, что именно происходит за кулисами вашего кода.

На практике, можно сократить код (или хотя бы количество строк), воспользовавшись тем, что появление ошибки из DB.QueryRow() откладываются до вызова Scan(). Функциональной разницы нет, так что если вы хотите, можете переписать код следующим образом:

Скачать исходный код урока

В конце каждой статьи мы предоставляем готовый исходный код на текущем этапе разработки.

Скачать: snippetbox-20.zip

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *