SQL и Qt

QSqlDatabase: подключение к SQLite, MySQL и PostgreSQL из Qt

Введение

Модуль Qt SQL предоставляет единый высокоуровневый интерфейс для работы с разными СУБД — SQLite, MySQL, PostgreSQL и другими — через систему драйверов. Это позволяет писать код, который при необходимости можно перенести с одной базы данных на другую с минимальными изменениями, поскольку основная логика взаимодействия (подключение, выполнение запросов, обработка результатов) остаётся одинаковой независимо от конкретной СУБД. В статье разберём, как подключаться к трём самым распространённым базам данных.

Концепция

QSqlDatabase::addDatabase(driverName) создаёт объект подключения, привязанный к конкретному драйверу — "QSQLITE" для SQLite, "QMYSQL" для MySQL, "QPSQL" для PostgreSQL. После создания объекта задаются параметры подключения (имя хоста, порт, имя базы данных, учётные данные), и вызывается open() для фактического установления соединения. Подключения именуются (по умолчанию — «безымянное» подключение, единственное на приложение) — для работы с несколькими базами одновременно используется именованный параметр addDatabase(driver, connectionName).

Пример кода: QSqlDatabase

#include <QCoreApplication>
#include <QSqlDatabase>
#include <QSqlError>
#include <QDebug>

bool connectSqlite()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "sqlite_connection");
    db.setDatabaseName("local_data.db");

    if (!db.open()) {
        qWarning() << "Ошибка подключения к SQLite:" << db.lastError().text();
        return false;
    }
    qDebug() << "Подключение к SQLite установлено";
    return true;
}

bool connectPostgres()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL", "postgres_connection");
    db.setHostName("localhost");
    db.setPort(5432);
    db.setDatabaseName("myapp_production");
    db.setUserName("app_user");
    db.setPassword(qgetenv("DB_PASSWORD")); // пароль из переменной окружения, не хардкод

    if (!db.open()) {
        qWarning() << "Ошибка подключения к PostgreSQL:" << db.lastError().text();
        return false;
    }
    qDebug() << "Подключение к PostgreSQL установлено";
    return true;
}

bool connectMysql()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "mysql_connection");
    db.setHostName("localhost");
    db.setDatabaseName("myapp");
    db.setUserName("app_user");
    db.setPassword(qgetenv("DB_PASSWORD"));
    db.setConnectOptions("MYSQL_OPT_RECONNECT=1");

    if (!db.open()) {
        qWarning() << "Ошибка подключения к MySQL:" << db.lastError().text();
        return false;
    }
    qDebug() << "Подключение к MySQL установлено";
    return true;
}

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    qDebug() << "Доступные драйверы:" << QSqlDatabase::drivers();
    connectSqlite();
    return 0;
}

Пояснения к коду

Каждая функция создаёт независимое именованное подключение через addDatabase(driver, connectionName), что позволяет приложению одновременно работать, например, с локальной SQLite-кэш базой и удалённой PostgreSQL — оба подключения существуют параллельно и не конфликтуют друг с другом. setConnectOptions("MYSQL_OPT_RECONNECT=1") в примере с MySQL — специфичная для драйвера опция, демонстрирующая, что несмотря на единый высокоуровневый интерфейс, некоторые тонкие настройки всё же специфичны для конкретной СУБД.

Пароль передаётся через qgetenv("DB_PASSWORD"), а не хардкодится в исходном коде — это базовая практика безопасности, предотвращающая попадание учётных данных в систему контроля версий.

Подводные камни

  • Хардкод учётных данных непосредственно в исходном коде, который попадает в репозиторий — классическая утечка секретов; пароли и строки подключения должны браться из переменных окружения, защищённого хранилища конфигурации или файлов, явно исключённых из системы контроля версий.
  • Отсутствие проверки результата open(). Без проверки булевого результата код может продолжить выполнение, считая, что подключение установлено, и последующие запросы будут проваливаться с малопонятными ошибками — каждый вызов open() должен сопровождаться проверкой и логированием db.lastError() при сбое.
  • Использование одного и того же безымянного подключения (без указания connectionName) из нескольких потоков. QSqlDatabase не является потокобезопасным для совместного использования одного подключения несколькими потоками одновременно — каждый поток, работающий с базой данных, должен иметь собственное именованное подключение.
  • Отсутствие драйвера для конкретной СУБД в сборке Qt. Драйверы QMYSQL/QPSQL часто требуют отдельной установки клиентских библиотек СУБД и могут не входить в стандартную сборку Qt по умолчанию на некоторых платформах — перед использованием стоит проверить QSqlDatabase::isDriverAvailable() и обеспечить наличие нужных системных библиотек на целевой машине.