Давайте обновим недавно созданный метод SnippetModel.Insert()
из файла pkg/models/mysql/snippets.go
, чтобы он создавал новую запись в таблице snippets
, и затем возвращал id
типа integer для только что созданной записи.
Содержание статьи
- Выполнение SQL запроса
- Использование модели базы данных в обработчиках
- Параметры плейсхолдера для веб-приложения на Go
Для этого требуется выполнить следующий SQL запрос к базе данных:
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
1 2 |
INSERT INTO snippets (title, content, created, expires) VALUES(?, ?, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL ? DAY)) |
Обратите внимание, что в этом запросе мы используем знак ?
для указания плейсхолдера для данных, которых требуется вставить в базу данных. Так как данные, которые мы будем использовать, в конечном итоге будут поступать к нам от пользователя из HTML формы, рекомендуется использовать плейсхолдеры вместо вставки данных напрямую в SQL запросе.
Если вы не будете использовать плейсхолдеры, то вы рискуете оставить уязвимость в виде SQL-инъекции.
Выполнение SQL запроса
Go предоставляет три различных метода для выполнения запросов к базе данных:
- DB.Query() используется для
SELECT
запросов, которые возвращают несколько рядов данных из таблицы, например «все записи за сегодня«; - DB.QueryRow() используется для
SELECT
запросов, которые возвращают один ряд, например «только одну запись у которой ID = 10«; - DB.Exec() используется для операторов, которые не возвращают данные (вроде
INSERT
иDELETE
). Добавление данных или их удаление из базы данных.
В нашем случае, подходящим инструментом будет DB.Exec()
. Давайте перейдем к сути и посмотрим, как мы применим его в методе SnippetModel.Insert()
. Подробности разберем чуть позже.
Откройте файл pkg/models/mysql/snippets.go
и обновите его следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package mysql import ( "database/sql" "golangify.com/snippetbox/pkg/models" ) // SnippetModel - Определяем тип который обертывает пул подключения sql.DB type SnippetModel struct { DB *sql.DB } // Insert - Метод для создания новой заметки в базе дынных. func (m *SnippetModel) Insert(title, content, expires string) (int, error) { // Ниже будет SQL запрос, который мы хотим выполнить. Мы разделили его на две строки // для удобства чтения (поэтому он окружен обратными кавычками // вместо обычных двойных кавычек). stmt := `INSERT INTO snippets (title, content, created, expires) VALUES(?, ?, UTC_TIMESTAMP(), DATE_ADD(UTC_TIMESTAMP(), INTERVAL ? DAY))` // Используем метод Exec() из встроенного пула подключений для выполнения // запроса. Первый параметр это сам SQL запрос, за которым следует // заголовок заметки, содержимое и срока жизни заметки. Этот // метод возвращает объект sql.Result, который содержит некоторые основные // данные о том, что произошло после выполнении запроса. result, err := m.DB.Exec(stmt, title, content, expires) if err != nil { return 0, err } // Используем метод LastInsertId(), чтобы получить последний ID // созданной записи из таблицу snippets. id, err := result.LastInsertId() if err != nil { return 0, err } // Возвращаемый ID имеет тип int64, поэтому мы конвертируем его в тип int // перед возвратом из метода. return int(id), nil } // Get - Метод для возвращения данных заметки по её идентификатору ID. func (m *SnippetModel) Get(id int) (*models.Snippet, error) { return nil, nil } // Latest - Метод возвращает 10 наиболее часто используемые заметки. func (m *SnippetModel) Latest() ([]*models.Snippet, error) { return nil, nil } |
Давайте быстро разберем интерфейс sql.Result, который мы получаем после выполнения DB.Exec()
. Он предоставляет два метода:
LastInsertId()
возвращает целое число (int64
), сгенерированное базой данных в ответ на выполненную команду вставки. Обычно это происходит в столбцеid
с «автоматическим инкрементом» при создании новой записи. Это наш случай;RowsAffected()
возвращает количество строк (в видеint64
), которые были как-то затронуты (изменены) после выполненных действий.
Важно: Не все драйверы и базы данных поддерживают методы
LastInsertId()
иRowsAffected()
. Например,LastInsertId()
не поддерживается в PostgreSQL. Если вы планируете использовать данные методы, то нужно сначала проверить документацию для вашего драйвера.
Но, лучше всего игнорировать возвращаемое значение из sql.Result
, если оно вам не нужно. Делается это таким образом:
1 |
_, err := m.DB.Exec("INSERT INTO ...", ...) |
Использование модели базы данных в обработчиках
Давайте вернемся к чему-то более конкретному и продемонстрируем, как вызывать методы моделей из наших обработчиков. Откройте файл cmd/web/handlers.go
и обновите обработчик createSnippet
следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
func (app *application) createSnippet(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.Header().Set("Allow", http.MethodPost) app.clientError(w, http.StatusMethodNotAllowed) return } // Создаем несколько переменных, содержащих тестовые данные. Мы удалим их позже. title := "История про улитку" content := "Улитка выползла из раковины,\nвытянула рожки,\nи опять подобрала их." expires := "7" // Передаем данные в метод SnippetModel.Insert(), получая обратно // ID только что созданной записи в базу данных. id, err := app.snippets.Insert(title, content, expires) if err != nil { app.serverError(w, err) return } // Перенаправляем пользователя на соответствующую страницу заметки. http.Redirect(w, r, fmt.Sprintf("/snippet?id=%d", id), http.StatusSeeOther) } |
Запустите веб-приложение из терминала:
1 |
go run ./cmd/web |
Затем откройте второе окно терминала и используйте curl
, чтобы сделать запрос POST /snippet/create
. Обратите внимание, что флаг -L
указывает curl на автоматическое выполнение перенаправления. Наше приложение в конце выполняет редирект на страницу заметки.
1 |
$ curl -iL -X POST http://127.0.0.1:4000/snippet/create |
Результат:
1 2 3 4 5 6 7 8 9 10 11 |
HTTP/1.1 303 See Other Location: /snippet?id=4 Date: Wed, 21 Apr 2021 14:39:12 GMT Content-Length: 0 HTTP/1.1 200 OK Date: Wed, 21 Apr 2021 14:39:12 GMT Content-Length: 101 Content-Type: text/plain; charset=utf-8 Отображение определенной заметки с идентификатором 4... |
Все работает довольно хорошо. Мы только что отправили HTTP запрос, который запустил обработчик createSnippet
, который вызвал метод SnippetModel.Insert()
. Он вставил новую запись в базу данных и вернул ID этой новой записи. Затем обработчик перенаправил на другой адрес с ID в конце URL строки.
Можете взглянуть на содержимое таблицы snippets
из базы данных MySQL.
Зайдите в MySQL через терминал:
1 |
mysql -u root -p |
Используйте пароль который вы указали при установки MySQL.
Выбираем базу данных:
1 |
mysql> USE snippetbox; |
Выполняем SELECT запрос, чтобы получить весь список заметок из таблицы:
1 |
mysql> SELECT id, title, expires FROM snippets; |
Результат:
1 2 3 4 5 6 7 8 9 |
+----+---------------------------------------------+---------------------+ | id | title | expires | +----+---------------------------------------------+---------------------+ | 1 | Не имей сто рублей | 2022-01-27 13:09:34 | | 2 | Лучше один раз увидеть | 2022-01-27 13:09:40 | | 3 | Не откладывай на завтра | 2021-02-03 13:09:44 | | 4 | История про улитку | 2021-04-28 14:39:12 | +----+---------------------------------------------+---------------------+ 4 rows in set (0.00 sec) |
Вот и наша запись в конце под номером 4.
Использования плейсхолдеров для SQL запросов
В приведенном выше коде мы составили SQL запрос, используя плейсхолдеры, где знак ?
выступал в качестве плейсхолдера для данных, которые требуется вставить.
Топорный перевод для плейсхолдера будет «придержи мне место» под будущие данные.
Причина использования плейсхолдера для построения запроса (а не интерполяции строк) заключается в том, что любые данные которые нужно вставить в базу данных нужно сперва «подготовить» прежде чем выполнить запрос. Даже если пользователь вместо заголовка заметки отправит какой-то вредоносный SQL запрос (SQL инъекция), то используя плейсхолдеры, эти запросы не навредят базе данных.
Под «капотом», метод DB.Exec()
работает в три этапа:
- Он создает новый подготовленный запрос в базу данных, используя предоставленный SQL оператор. База данных парсирует и компилирует запрос, а затем хранит его готовым к выполнению;
- На втором шаге,
Exec()
передает значения параметров в базу данных. Затем база данных выполняет подготовленный запрос, используя эти параметры. Параметры передаются позже, поэтому после компиляции запроса, база данных рассматривает их как чистые данные. Они не могут изменить цель оператора. До тех пор, пока исходный оператор получен из ненадежных данных, инъекция не может произойти; - Затем он закрывает подготовленный запрос к базе данных.
Синтаксис параметра плейсхоледера различается в зависимости от базы данных. MySQL, SQL Server и SQLite используют символ ?
, но PostgreSQL использует $N
. К примеру , если бы вы использовали PostgreSQL, вы бы создали запись таким образом:
1 |
_, err := m.DB.Exec("INSERT INTO ... VALUES ($1, $2, $3)", ...) |
Исходный код
В конце каждого урока мы предоставляем актуальный код на момент написания данной статьи.
Скачать: snippetbox-19.zip
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»