Введение
Основной цикл статей (статья 24, БД/ORM) уже касался работы с БД из Qt — в этой статье систематизируем практический инструментарий Qt для работы с реляционными БД: QSqlQuery для прямого выполнения SQL (включая параметризованные запросы из статьи 433), и QSqlTableModel/QSqlRelationalTableModel как Qt-специфичный, более высокоуровневый подход, концептуально близкий к лёгкому ORM (статья 436), но интегрированный непосредственно с моделями Qt для отображения в табличных виджетах.
Концепция
QSqlQuery — прямой, низкоуровневый интерфейс выполнения произвольного SQL, дающий полный контроль над запросом (аналогично прямому SQL из всего предыдущего цикла), но требующий явного написания каждого запроса. QSqlTableModel оборачивает обращение к конкретной таблице БД в интерфейс модели Qt (QAbstractTableModel), что позволяет напрямую связать таблицу БД с табличным виджетом (QTableView) без написания отдельного кода для загрузки/отображения/сохранения данных — изменения, сделанные пользователем непосредственно в виджете, автоматически транслируются в соответствующие SQL-команды модели. QSqlRelationalTableModel расширяет это дополнительной поддержкой внешних ключей (статья 397), автоматически отображая связанные значения (например, имя клиента вместо его числового customer_id) непосредственно в табличном представлении.
Пример кода: QSqlQuery и QSqlTableModel в Qt
// QSqlQuery — прямое выполнение запроса с обработкой результата построчно
QSqlQuery query;
query.prepare("SELECT id, name, total_amount FROM orders WHERE customer_id = :id");
query.bindValue(":id", 5);
if (query.exec()) {
while (query.next()) {
int orderId = query.value("id").toInt();
QString name = query.value("name").toString();
double amount = query.value("total_amount").toDouble();
qDebug() << orderId << name << amount;
}
} else {
qWarning() << "Ошибка запроса:" << query.lastError().text();
}
// QSqlTableModel — связывание таблицы БД с QTableView без написания SQL вручную
#include <QSqlTableModel>
#include <QTableView>
void setupTableModel(QTableView *view)
{
auto *model = new QSqlTableModel();
model->setTable("orders");
model->setEditStrategy(QSqlTableModel::OnFieldChange); // изменения сохраняются автоматически
model->select(); // выполняет SELECT * FROM orders под капотом
view->setModel(model);
// Пользователь может редактировать данные прямо в таблице интерфейса —
// модель автоматически генерирует соответствующие UPDATE-команды
}
// QSqlRelationalTableModel — автоматическое отображение связанных данных через внешний ключ
#include <QSqlRelationalTableModel>
#include <QSqlRelationalDelegate>
void setupRelationalModel(QTableView *view)
{
auto *model = new QSqlRelationalTableModel();
model->setTable("orders");
model->setRelation(1, QSqlRelation("customers", "id", "name")); // столбец 1 = customer_id
model->select();
view->setModel(model);
view->setItemDelegate(new QSqlRelationalDelegate(view)); // показывает имя клиента, не его id,
// и даёт выбор из выпадающего списка при редактировании
}
Пояснения к коду
QSqlQuery::prepare()/bindValue() показывает уже знакомый из статьи 433 паттерн параметризованного запроса — QSqlQuery остаётся базовым строительным блоком даже при использовании более высокоуровневых моделей, и понимание его API необходимо для любых случаев, не покрываемых удобствами QSqlTableModel. QSqlTableModel::setEditStrategy(OnFieldChange) показывает декларативную настройку поведения сохранения изменений — вместо явного написания кода обработки сигналов изменения данных виджета и соответствующего вызова UPDATE, разработчик просто настраивает желаемую стратегию, и остальное берёт на себя сама модель. QSqlRelationalTableModel::setRelation() явно демонстрирует Qt-специфичный механизм работы со связанными данными — концептуально похожий на JOIN (статья 398), но выраженный через объектно-ориентированный API модели, а не явный SQL-запрос, и сопровождаемый QSqlRelationalDelegate, автоматически предоставляющим удобный выпадающий список для выбора связанного значения непосредственно в табличном виджете.
Подводные камни
- Использование
QSqlTableModelдля сложных, многотабличных операций, требующих явного JOIN или агрегации, для которых эта модель не предназначена —QSqlTableModelориентирован на простой, прямой доступ к одной конкретной таблице (с опциональным расширением черезQSqlRelationalTableModelдля отображения, но не для сложной агрегации), и для запросов, требующих более сложной логики (статьи 398-405), стоит либо использоватьQSqlQueryModel(read-only модель, принимающая произвольный, написанный вручную SQL-запрос), либо явныйQSqlQueryс собственной логикой представления данных. - Установка
EditStrategy::OnFieldChangeдля таблицы с большим количеством одновременных, быстрых изменений (например, программная массовая правка многих ячеек подряд), что приводит к отдельной SQL-командеUPDATEдля каждого отдельного изменённого поля — для случаев, требующих массовых изменений, стоит рассмотретьOnManualSubmit(накопление изменений локально, с последующим явным, единократным вызовомsubmitAll()для отправки всех накопленных изменений за один проход), что значительно эффективнее множества отдельных команд для каждого отдельного изменения. - Игнорирование обработки ошибок при работе с
QSqlTableModel, предполагая, что операции (select(), изменение данных через интерфейс) всегда успешны — аналогично явной проверкеquery.exec()в примере сQSqlQuery, операции модели также могут завершиться неудачей (нарушение ограничения уникальности, проблема соединения с БД), и приложение должно явно проверятьmodel->lastError()после потенциально неудачных операций, а не предполагать их безусловный успех. - Смешивание прямых SQL-запросов через отдельный
QSqlQueryи операций черезQSqlTableModelдля одной и той же таблицы без понимания возможной несогласованности их представлений о текущем состоянии данных — если данные изменены напрямую через отдельныйQSqlQuery(минуя модель), уже существующийQSqlTableModel, отображающий ту же таблицу, не узнает об этом изменении автоматически и продолжит показывать устаревшие, закешированные внутри модели данные до явного повторного вызоваselect()для обновления её внутреннего состояния.