[Предыдущая: Модуль QtOpenGL] [Модули Qt] [Следующая: Модуль QtSvg] Модуль QtSql
Модуль QtSql помогает обеспечить однородную интеграцию БД в ваши Qt приложения. Далее... Пространства имен
Классы
Подробное описаниеКлассы SQL подразделяются на три слоя:
Помните, что до использования любого из этих классов должен быть инициализирован объект класса QCoreApplication. Для включения определений классов этого модуля используйте следующую директиву: #include <QtSql> Для линковки приложения с этим модулем, добавьте в ваш qmake файл проекта .pro: QT += sql Модуль QtSql входит в состав Qt Console Edition, Qt Desktop Edition и Qt Open Source Edition. Данный обзор предполагает, что вы имеете, по крайней мере, базовые знания об SQL. Вы должны понимать простые выражения SELECT, INSERT, UPDATE и DELETE. Несмотря на то, что класс QSqlTableModel предоставляет интерфейс для просмотра и редактирования базы данных без знания SQL, наличие базовых представлений об SQL настоятельно рекомендуется. Стандартное описание баз данных SQL представлено в An Introduction to Database Systems (7th Ed.) by C. J. Date, ISBN 0201385902. Темы:
Соединение с базой данныхПрежде, чем получить доступ к базе данных с помощью классов QSqlQuery или QSqlQueryModel, вы должны установить с базой данных хотя бы одно соединение. Соединения с базой данных идентифицируются с помощью произвольных строк. QSqlDatabase также поддерживает концепцию соединения по умолчанию, которое используется классом Qt SQL, если никакое другое соединение не указано. Этот механизм очень удобен для приложений, использующих только одно соединение с базой данных. Следующий код устанавливает соединение с базой данных MySQL с именем flightdb на узле bigblue: QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL"); db.setHostName("bigblue"); db.setDatabaseName("flightdb"); db.setUserName("acarlson"); db.setPassword("1uTbSbAs"); bool ok = db.open(); Первый аргумент QSqlDatabase::addDatabase() - это имя драйвера. Для получения списка драйверов, смотрите документацию addDatabase(). Для инициализации данных соединения мы вызываем setHostName(), setDatabaseName(), setUserName() и setPassword(). Так как имя соединения не определено, соединение становится соединением по умолчанию. Для определения имени передайте его в качестве второго аргумента в QSqlDatabase::addDatabase(). Например: QSqlDatabase firstDB = QSqlDatabase::addDatabase("QMYSQL", "first"); QSqlDatabase secondDB = QSqlDatabase::addDatabase("QMYSQL", "second"); Как только соединение инициализировано, мы должны вызвать QSqlDatabase::open() для открытия БД и получения доступа к данным. Если запрос соединения потерпит неудачу, функция возвратит false; информацию об ошибке можно получить из QSqlDatabase::lastError(). Как только соединение установлено, мы можем получить указатель на него из любого места прораммы, вызвав статическую функцию QSqlDatabase::database(). Если мы вызываем данную функцию без параметра, она возвратит указатель на соединение по умолчанию. Если функция вызывается с идентификатором, использованным при установке соединения, она возвратит ссылку на указанное соединение. Например: QSqlDatabase defaultDB = QSqlDatabase::database(); QSqlDatabase firstDB = QSqlDatabase::database("first"); QSqlDatabase secondDB = QSqlDatabase::database("second"); Для удаления соединения с базой данных, сначала закройте базу данных с помощью QSqlDatabase::close(), а затем, удалите ее с помощью статического метода QSqlDatabase::removeDatabase(). Выполнение инструкций SQLКласс QSqlQuery обеспечивает интерфейс для выполнения SQL запросов и навигации по результирующей выборке. Классы QSqlQueryModel и QSqlTableModel, описанные в следующем разделе, предоставляют высокоуровневый интерфейс для доступа к базам данных. Если вы не знакомы с SQL, вам, возможно, захочется сразу перейти к следующему разделу (Использование классов SQL модели). Выполнение запросаДля выполнения SQL запросов, просто создают объект QSqlQuery и вызывают QSqlQuery::exec(). Например, вот так: QSqlQuery query; query.exec("SELECT name, salary FROM employee WHERE salary > 50000"); Конструктор QSqlQuery принимает необязательный аргумент QSqlDatabase, который указывает, какое соединение с базой данных используется. В нижеприведенном примере мы не указываем соединение, поэтому используется соединение по умолчанию. Если возникает ошибка, exec() возвращает false. Доступ к ошибке можно получить с помощью QSqlQuery::lastError(). Навигация по результирующей выборкеQSqlQuery предоставляет единовременный доступ к результирующей выборке одного запроса. После вызова exec(), внутренний указатель QSqlQuery указывает на позицию перед первой записью. Мы должны вызвать метод QSqlQuery::next() один раз, чтобы переместить указатель к первой записи, затем снова повторять вызов next(), чтобы получать доступ к другим записям, до тех пор пока он не вернет false. Вот типичный цикл, перебирающий все записи по порядку: while (query.next()) { QString name = query.value(0).toString(); int salary = query.value(1).toInt(); qDebug() << name << salary; } Функция QSqlQuery::value() возвращает значение поля текущей записи. Поля задаются индексами, начиная с нуля. Функция QSqlQuery::value() возвращает значение типа QVariant, который может хранить значения различных типов C++ и ядра Qt, такие как int, QString и QByteArray. Различные типы значений базы данных автоматически приводятся к ближайшему эквиваленту в Qt. В данном фрагменте кода мы вызываем QVariant::toString() и QVariant::toInt() для преобразования переменных в QString и int. Вы можете перемещаться взад и вперед по выборке, используя функции QSqlQuery::next(), QSqlQuery::previous(), QSqlQuery::first(), QSqlQuery::last() и QSqlQuery::seek(). Текущий номер строки можно получить с помощью QSqlQuery::at(), а общее количество строк в выборке, если это поддерживается базой данных, возвращается функцией QSqlQuery::size(). Определить, поддерживает ли драйвер базы данных определенную особенность можно с помощью вызова функции QSqlDriver::hasFeature(). В следующем примере мы вызываем QSqlQuery::size() для определения размера результирующей выборке, только в том случае, если база данных поддерживает такую возможность; в противном случае мы перемещаемся к последней записи и используем ее позицию в выборке для определения количества записей. QSqlQuery query;
int numRows;
query.exec("SELECT name, salary FROM employee WHERE salary > 50000");
QSqlDatabase defaultDB = QSqlDatabase::database();
if (defaultDB.driver()->hasFeature(QSqlDriver::QuerySize)) {
numRows = query.size();
} else {
// это может занять немало времени
query.last();
numRows = query.at() + 1;
}
Если вы перебираете результирующую выборку только с помощью вызовов next() и seek() с положительными значениями, то можете перед вызовом exec() вызвать QSqlQuery::setForwardOnly(true). Эта небольшая оптимизация сильно ускорит выполнение запросов, возвращающих большие выборки. Вставка, изменение и удаление записейQSqlQuery может выполнять любые SQL выражения, а не просто SELECT'ы. Следующий пример вставляет запись в таблицу, используя INSERT: QSqlQuery query; query.exec("INSERT INTO employee (id, name, salary) " "VALUES (1001, 'Thad Beaumont', 65000)"); Если вы хотите одновременно вставить множество записей, то зачастую эффективней отделить запрос от реально вставляемых значений. Это можно сделать с помощью вставки значений через параметры. Qt поддерживает два синтаксиса вставки значений: поименованные параметры и позиционные параметры. В следующем примере показана вставка с помощью поименованного параметра: QSqlQuery query; query.prepare("INSERT INTO employee (id, name, salary) " "VALUES (:id, :name, :salary)"); query.bindValue(":id", 1001); query.bindValue(":name", "Thad Beaumont"); query.bindValue(":salary", 65000); query.exec(); В этом примере показана вставка с помощью позиционного параметра: QSqlQuery query; query.prepare("INSERT INTO employee (id, name, salary) " "VALUES (?, ?, ?)"); query.addBindValue(1001); query.addBindValue("Thad Beaumont"); query.addBindValue(65000); query.exec(); Оба синтаксиса работают со всеми драйверами баз данных предоставляемыми Qt. Если база данных поддерживает синтаксис, Qt просто пересылает запрос в СУБД; в противном случае, Qt симулирует синтаксис параметров и осуществляет предобработку запроса. Фактический запрос, который поступает на выполнение в СУБД доступен с помощью QSqlQuery::executedQuery(). При вставке множества записей, вам требуется вызвать QSqlQuery::prepare() только однажды. Далее вы можете вызвать bindValue() или addBindValue() с последующим вызовом exec() столько раз, сколько потребуется. Помимо удобства выполнения, вставка через параметры имеет еще и то преимущество, что вы избавлены от необходимости заботиться о преобразовании специальных символов. Изменение записей очень похоже на вставку в таблицу: QSqlQuery query; query.exec("UPDATE employee SET salary = 70000 WHERE id = 1003"); Вы также можете использовать поименованную или позиционную вставку для ассоциирования параметров строки запроса с актуальными значениями. В конце приведем пример выражения DELETE: QSqlQuery query; query.exec("DELETE FROM employee WHERE id = 1007"); ТранзакцииЕсли основной движок базы данных поддерживает транзакции, то QSqlDriver::hasFeature(QSqlDriver::Transactions) возвратит true. Для инициации транзакции вы можете использовать QSqlDatabase::transaction(), затем запустить инструкции SQL, которые вы хотите выполнить в пределах транзакции, а после вызвать QSqlDatabase::commit() или QSqlDatabase::rollback(). При использовании транзакции вы должны начать её, прежде чем создадите свой запрос. Пример: QSqlDatabase::database().transaction(); QSqlQuery query; query.exec("SELECT id FROM employee WHERE name = 'Torild Halvorsen'"); if (query.next()) { int employeeId = query.value(0).toInt(); query.exec("INSERT INTO project (id, name, ownerid) " "VALUES (201, 'Manhattan Project', " + QString::number(employeeId) + ")"); } QSqlDatabase::database().commit(); Транзакции можно использовать для того, чтобы гарантировать атомарность сложного действия (например, просмотр внешних ключей и создание записи) или для возможности отмены сложного действия в процессе его выполнения. Использование классов-моделей SQLВ дополнение к QSqlQuery Qt предлагает три высокоуровневых класса для работы с базами данных. Это классы QSqlQueryModel, QSqlTableModel и QSqlRelationalTableModel.
Эти классы происходят от QAbstractTableModel (который происходит от QAbstractItemModel) и могут существенно облегчить представление данных из базы данных в элементно-ориентированных классах таких, как QListView и QTableView. Это подробно объясняется в разделе Отображение данных в таблице-представлении. Другое преимущество использования этих классов состоит в том, что они облегчают приспособление кода к другому источнику данных. Например, если вы использовали QSqlTableModel, а затем решили вместо базы данных использовать XML-файлы для хранения данных, то изменение кода - это вопрос замены одной модели данных на другую. Использование модели запроса SQLQSqlQueryModel предлагает основанную на SQL запросе модель только-для-чтения. Пример: QSqlQueryModel model; model.setQuery("SELECT * FROM employee"); for (int i = 0; i < model.rowCount(); ++i) { int id = model.record(i).value("id").toInt(); QString name = model.record(i).value("name").toString(); qDebug() << id << name; } После настройки запроса с помощью QSqlQueryModel::setQuery(), вы можете использовать QSqlQueryModel::record(int) для получения доступа к отдельным записям. Также можете использовать QSqlQueryModel::data() и другие функции, унаследованные от QAbstractItemModel. Также есть перегруженный метод setQuery(), который принимает объект QSqlQuery и работает с его результирующей выборкой. Это позволяет вам использовать любые возможности QSqlQuery для установки запроса (например, подготовленные запросы QSqlQuery::prepare()). Модель таблицы SQLQSqlTableModel предлагает модель для чтения и записи, которая работает одновременно только с одной таблицей SQL. Пример: QSqlTableModel model; model.setTable("employee"); model.setFilter("salary > 50000"); model.setSort(2, Qt::DescendingOrder); model.select(); for (int i = 0; i < model.rowCount(); ++i) { QString name = model.record(i).value("name").toString(); int salary = model.record(i).value("salary").toInt(); qDebug() << name << salary; } QSqlTableModel - это высокоуровневая альтернатива QSqlQuery для изменения отдельной SQL таблицы и навигации по ней. Обычно это требует меньше кода и не требует знания SQL синтаксиса. Для получения строки таблицы используйте QSqlTableModel::record(), а для ее изменения - QSqlTableModel::setRecord(). Например, следующий код увеличивает зарплату всех сотрудников на 10 процентов: for (int i = 0; i < model.rowCount(); ++i) { QSqlRecord record = model.record(i); double salary = record.value("salary").toInt(); salary *= 1.1; record.setValue("salary", salary); model.setRecord(i, record); } model.submitAll(); Для доступа к данным вы можете также использовать QSqlTableModel::data() и QSqlTableModel::setData(), которые унаследованы от QAbstractItemModel. В следующем примере показано, как изменить запись с помощью setData(): model.setData(model.index(row, column), 75000); model.submitAll(); Здесь показано, как вставить строку и заполнить ее: model.insertRows(row, 1); model.setData(model.index(row, 0), 1013); model.setData(model.index(row, 1), "Peter Gordon"); model.setData(model.index(row, 2), 68500); model.submitAll(); Пример удаления пяти следующих друг за другом строк: model.removeRows(row, 5); model.submitAll(); В качестве первого элемента QSqlTableModel::removeRows() передается индекс первой удаляемой строки. После окончания редактирования записи, вы должны вызвать QSqlTableModel::submitAll() для того, чтобы гарантировать запись изменений в базу данных. Когда и необходимо ли вообще вам вызывать submitAll(), зависит от стратегии редактирования таблицы. По умолчанию установлена стратегия QSqlTableModel::OnRowChange, которая указывает, что произведенные изменения будут внесены в базу данных при выборе пользователем другой строки. Другие стратегии - это QSqlTableModel::OnManualSubmit (все изменения кэшируются в модели до вызова submitAll()) и QSqlTableModel::OnFieldChange (где изменения не кэшируются). Они, в основном, полезны, когда используется QSqlTableModel с представлением. Хотя кажется, что QSqlTableModel::OnFieldChange обещает, что вам никогда не придется вызывать submitAll(). Есть два нюанса:
Модель реляционной таблицы SQLQSqlRelationalTableModel расширяет QSqlTableModel для предоставления поддержки внешних ключей. Внешний ключ - это связь один-к-одному, установленная между полем одной таблицы и полем первичного ключа другой таблицы. Например, если таблица book имеет поле с названием authorid, которое ссылается на поле id таблицы авторов, вы указываете, что поле authorid является первичным ключом. Левый скриншот показывает QSqlTableModel в QTableView. Внешние ключи (city и country) не представлены удобочитаемыми значениями. Правый скриншот показывает QSqlRelationalTableModel, в котором внешние ключи представлены удобочитаемыми текстовыми строками. Следующий отрывок кода демонстрирует настройку QSqlRelationalTableModel: model->setTable("employee"); model->setRelation(2, QSqlRelation("city", "id", "name")); model->setRelation(3, QSqlRelation("country", "id", "name")); Более подробную информацию можно найти в описании QSqlRelationalTableModel. Отображение данных в таблице-представленииКлассы QSqlQueryModel, QSqlTableModel и QSqlRelationalTableModel могут использоваться в качестве источников данных для классов представлений Qt, таких как QListView, QTableView и QTreeView. На практике наиболее часто используется QTableView в связи с тем, что результирующая SQL выборка, по существу, представляет собой двумерную структуру данных. В следующем примере создается представление основанное на модели данных SQL: QTableView *view = new QTableView; view->setModel(model); view->show(); Если модель является моделью для чтения-записи (например, QSqlTableModel), то представление позволяет редактировать поля. Вы можете отключить это с помощью следующего кода view->setEditTriggers(QAbstractItemView::NoEditTriggers); Вы можете использовать одну и ту-же модель в качестве источника данных для нескольких представлений. Если пользователь изменяет данные модели с помощью одного из представлений, другие представления немедленно отобразят изменения. Пример Table Model демонстрирует, как это работает. Классы-представления для обозначения колонок наверху отображают заголовки. Для изменения текста заголовка, вызовите setHeaderData() модели. Текстом заголовка по умолчанию является наименование поля. Например: model->setHeaderData(0, Qt::Horizontal, QObject::tr("ID")); model->setHeaderData(1, Qt::Horizontal, QObject::tr("Name")); model->setHeaderData(2, Qt::Horizontal, QObject::tr("City")); model->setHeaderData(3, Qt::Horizontal, QObject::tr("Country")); QTableView также имеет вертикальные заголовки слева, содержащие номера строк. Если вы программно вставляете строки с помощью QSqlTableModel::insertRows(), новые колонки будут обозначены звездочкой (*) до тех пор, пока они не будут помещены в базу данных с помощью submitAll() или автоматически при переходе пользователя к другой записи (если стратегия редактирования равна QSqlTableModel::OnRowChange). Аналогично, если вы удаляете строки с помощью removeRows(), они будут отмечены восклицательным знаком (!), пока изменения не будут внесены в базу данных. Элементы представления отображаются с помощью делегата. Делегат по умолчанию QItemDelegate содержит большинство общих типов данных (int, QString, QImage и т.д.). Также делегат отвечает за предоставление виджета-редактора (например, combobox), когда пользователь начинает редактировать элемент представления. Вы можете создавать свои собственные делегаты, наследуя QAbstractItemDelegate или QItemDelegate. Для получения более подробной информации смотрите Программирование с помощью модели/представления. QSqlTableModel оптимизирован для работы с одной таблицей в один момент времени. Если Вам нужна модель для чтения-записи, которая может работать с произвольной результирующей выборкой, вы можете создать подкласс QSqlQueryModel и переопределить flags() и setData() для возможности осуществлять чтение-запись. Две следующие функции делают поля 1 и 2 модели SQL запроса редактируемыми: Qt::ItemFlags EditableSqlModel::flags(
const QModelIndex &index) const
{
Qt::ItemFlags flags = QSqlQueryModel::flags(index);
if (index.column() == 1 || index.column() == 2)
flags |= Qt::ItemIsEditable;
return flags;
}
bool EditableSqlModel::setData(const QModelIndex &index, const QVariant &value, int /* role */)
{
if (index.column() < 1 || index.column() > 2)
return false;
QModelIndex primaryKeyIndex = QSqlQueryModel::index(index.row(), 0);
int id = data(primaryKeyIndex).toInt();
clear();
bool ok;
if (index.column() == 1) {
ok = setFirstName(id, value.toString());
} else {
ok = setLastName(id, value.toString());
}
refresh();
return ok;
}
Вспомогательная функция setFirstName() определяется следующим образом: bool EditableSqlModel::setFirstName(int personId, const QString &firstName) { QSqlQuery query; query.prepare("update person set firstname = ? where id = ?"); query.addBindValue(firstName); query.addBindValue(personId); return query.exec(); } Функция setLastName() является подобной. Полный код реализации можно посмотреть в примере Query Model. Создание подклассов модели делает возможной настройку собственной модели различными способами: вы можете предоставить подсказки к элементам, изменить цвет фона, отобразить вычисляемые значения, разрешать различным значениям быть отображенными и отредактированными, отдельно обработать нулевые значения и т.д. Для получения более подробной информации смотрите Программирование модель/представление и описание класса QAbstractItemView. Если всё, что вам требуется - это разрешить внешний ключ для более понятного представления, вы можете использовать QSqlRelationalTableModel. Для получения лучшего результата, вам следует использовать делегат QSqlRelationalDelegate, который предоставляет для изменения значения внешнего ключа виджет-редактор в виде выпадающего списка. Пример Relational Table Model иллюстрирует использование QSqlRelationalTableModel в совокупности с QSqlRelationalDelegate для представления таблицы с поддержкой внешнего ключа. Создание информационно-связанных формИспользование SQL моделей описано выше, содержимое базы данных может быть представлено в других компонентах модель-представление. Для некоторых приложений достаточно, чтобы эти данные были представлены с помощью стандартных элементно-ориентированных представлений, таких как QTableView. Однако, пользователям приложений, основанных на манипулировании записями, часто требуется пользовательский интерфейс, содержащий формы, в котором данные из определенной строки или колонки в таблице базы данных используются, чтобы заполнять виджет-редактор на форме. Такие информационно-связанные формы могут быть созданы с помощью класса QDataWidgetMapper, общего компонента модели-представления, который используется, чтобы отобразить данные из модели в определенный виджет в пользовательском интерфейсе. QDataWidgetMapper работает с определенной таблицей базы данных, отображая элементы таблицы в базисе строка за строкой (row-by-row) или колонка за колонкой (column-by-column). В результате использование QDataWidgetMapper с SQL моделью так же просто, как использование его с любыми другими табличными моделями. Демонстрация Books показывает, как информация может быть представлена для легкого доступа, используя QDataWidgetMapper и набор простых виджетов ввода. [Предыдущая: Модуль QtOpenGL] [Модули Qt] [Следующая: Модуль QtSvg]
|
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |