Введение в sqlx: мощное расширение database/sql для Go
Библиотека sqlx — это популярное расширение стандартного пакета database/sql в языке Go. Она не заменяет стандартный драйвер, а надстраивается поверх него, добавляя удобные функции для работы с базами данных. Если вы когда-либо писали на Go и сталкивались с необходимостью вручную сканировать строки результата запроса в структуры, то sqlx покажется вам настоящим спасением.
Основная цель sqlx — упростить и ускорить разработку, избавив разработчика от шаблонного кода. Библиотека позволяет:
- Автоматически маппить результаты SQL-запросов в Go-структуры.
- Работать с именованными параметрами в запросах.
- Выполнять массовые вставки данных (batch insert).
- Использовать удобные методы для работы с prepared statements.
- Интегрироваться с любыми драйверами, совместимыми с
database/sql(PostgreSQL, MySQL, SQLite, и др.).
На сайте PythonLib.ru мы часто рассказываем о полезных инструментах для разработчиков. Сегодня мы подробно разберем sqlx — библиотеку, которая обязательна к изучению каждому Go-разработчику, работающему с базами данных.
Установка sqlx
Установка sqlx в проект на Go выполняется стандартным способом через менеджер пакетов go get:
go get github.com/jmoiron/sqlx
Также вам потребуется установить драйвер для конкретной базы данных. Например, для PostgreSQL:
go get github.com/lib/pq
Для MySQL:
go get github.com/go-sql-driver/mysql
Для SQLite:
go get github.com/mattn/go-sqlite3
После установки вы можете импортировать библиотеку в свой код:
import (
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // драйвер PostgreSQL
)
Быстрый старт: первый пример с sqlx
Давайте напишем простую программу, которая подключается к базе данных PostgreSQL, создает таблицу, вставляет несколько записей и выводит их на экран.
package main
import (
"fmt"
"log"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
type User struct {
ID int `db:"id"`
Name string `db:"name"`
Age int `db:"age"`
}
func main() {
// Подключение к базе данных
db, err := sqlx.Connect("postgres", "user=postgres dbname=testdb sslmode=disable")
if err != nil {
log.Fatalln(err)
}
defer db.Close()
// Создание таблицы
schema := `CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
age INTEGER NOT NULL
);`
db.MustExec(schema)
// Вставка данных
db.MustExec("INSERT INTO users (name, age) VALUES ($1, $2)", "Alice", 30)
db.MustExec("INSERT INTO users (name, age) VALUES ($1, $2)", "Bob", 25)
// Выборка данных
var users []User
err = db.Select(&users, "SELECT id, name, age FROM users")
if err != nil {
log.Fatalln(err)
}
fmt.Printf("Users: %+v\n", users)
}
В этом примере мы видим ключевые возможности sqlx: автоматическое маппинг полей структуры (благодаря тегам db), метод Select для выборки нескольких строк, и MustExec для выполнения запросов без обработки ошибок (удобно для прототипирования).
Основные методы и функции sqlx
Рассмотрим 10 ключевых методов библиотеки sqlx с подробным описанием и примерами.
1. sqlx.Connect
Что делает: Устанавливает соединение с базой данных и проверяет его (выполняет ping). Это самый удобный способ подключения.
Параметры:
driverName(string) — имя драйвера (например, "postgres", "mysql").dataSourceName(string) — строка подключения к БД.
Пример:
db, err := sqlx.Connect("postgres", "user=postgres dbname=mydb sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
2. sqlx.Open
Что делает: Создает пул соединений, но не проверяет подключение. Аналог database/sql.Open, но возвращает *sqlx.DB.
Параметры: Те же, что и у Connect.
Пример:
db, err := sqlx.Open("postgres", "user=postgres dbname=mydb sslmode=disable")
if err != nil {
log.Fatal(err)
}
// Проверка соединения вручную
err = db.Ping()
if err != nil {
log.Fatal(err)
}
3. DB.Select
Что делает: Выполняет запрос и маппит все строки результата в срез структур. Один из самых часто используемых методов.
Параметры:
dest(interface{}) — указатель на срез структур.query(string) — SQL-запрос.args(...interface{}) — аргументы запроса.
Пример:
var users []User
err := db.Select(&users, "SELECT * FROM users WHERE age > $1", 18)
if err != nil {
log.Fatal(err)
}
for _, u := range users {
fmt.Println(u.Name)
}
4. DB.Get
Что делает: Выполняет запрос и маппит первую строку результата в структуру. Если строк нет — возвращает ошибку sql.ErrNoRows.
Параметры:
dest(interface{}) — указатель на структуру.query(string) — SQL-запрос.args(...interface{}) — аргументы запроса.
Пример:
var user User
err := db.Get(&user, "SELECT * FROM users WHERE id = $1", 1)
if err != nil {
log.Fatal(err)
}
fmt.Printf("User: %+v\n", user)
5. DB.Exec
Что делает: Выполняет запрос, не возвращающий строки (INSERT, UPDATE, DELETE). Возвращает результат выполнения.
Параметры:
query(string) — SQL-запрос.args(...interface{}) — аргументы запроса.
Пример:
result, err := db.Exec("UPDATE users SET age = $1 WHERE id = $2", 35, 1)
if err != nil {
log.Fatal(err)
}
rowsAffected, _ := result.RowsAffected()
fmt.Printf("Обновлено строк: %d\n", rowsAffected)
6. DB.MustExec
Что делает: Аналог Exec, но паникует при ошибке. Удобно для инициализации схемы или тестов.
Параметры: Те же, что у Exec.
Пример:
db.MustExec("CREATE TABLE IF NOT EXISTS items (id SERIAL PRIMARY KEY, name TEXT)")
db.MustExec("INSERT INTO items (name) VALUES ($1)", "Laptop")
7. DB.NamedExec
Что делает: Выполняет запрос с именованными параметрами. Позволяет использовать имена полей структуры вместо позиционных плейсхолдеров.
Параметры:
query(string) — SQL-запрос с именованными параметрами (например,:name).arg(interface{}) — структура или карта с данными.
Пример:
type Product struct {
Name string `db:"name"`
Price float64 `db:"price"`
}
product := Product{Name: "Phone", Price: 999.99}
db.NamedExec("INSERT INTO products (name, price) VALUES (:name, :price)", product)
8. DB.NamedQuery
Что делает: Выполняет запрос с именованными параметрами и возвращает строки для итерации.
Параметры:
query(string) — SQL-запрос.arg(interface{}) — структура или карта.
Пример:
rows, err := db.NamedQuery("SELECT * FROM products WHERE price > :minPrice", map[string]interface{}{
"minPrice": 500,
})
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var p Product
err := rows.StructScan(&p)
if err != nil {
log.Fatal(err)
}
fmt.Println(p.Name)
}
9. DB.PrepareNamed
Что делает: Создает подготовленный запрос с именованными параметрами. Возвращает *sqlx.NamedStmt.
Параметры:
query(string) — SQL-запрос.
Пример:
stmt, err := db.PrepareNamed("INSERT INTO users (name, age) VALUES (:name, :age)")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
user := User{Name: "Charlie", Age: 28}
stmt.Exec(user)
10. sqlx.In
Что делает: Вспомогательная функция для построения запросов с динамическим списком IN. Позволяет безопасно передавать срез значений.
Параметры:
query(string) — SQL-запрос с плейсхолдером?или$1.args(...interface{}) — аргументы, включая срезы.
Пример:
ids := []int{1, 2, 3, 5}
query, args, err := sqlx.In("SELECT * FROM users WHERE id IN (?)", ids)
if err != nil {
log.Fatal(err)
}
// Для PostgreSQL нужно переформатировать плейсхолдеры
query = db.Rebind(query)
var users []User
err = db.Select(&users, query, args...)
if err != nil {
log.Fatal(err)
}
Реальный практический пример: CRUD-приложение для управления задачами
Давайте создадим полноценное консольное приложение для управления списком задач (TODO). Мы будем использовать PostgreSQL и sqlx.
package main
import (
"fmt"
"log"
"os"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
type Task struct {
ID int `db:"id"`
Title string `db:"title"`
Completed bool `db:"completed"`
}
var db *sqlx.DB
func initDB() {
var err error
db, err = sqlx.Connect("postgres", "user=postgres dbname=todoapp sslmode=disable")
if err != nil {
log.Fatal(err)
}
schema := `CREATE TABLE IF NOT EXISTS tasks (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
completed BOOLEAN DEFAULT FALSE